BluNotify Partner API — Reference

Every route, parameter and field, generated from /openapi.json (version 1.0.0). Guides: quick start · BluEM quick start · full guide · changelog

REST API for approved partners to work with BluNotify devices in their scope: notification lights (BluNotify) and energy monitors (BluEM, under /v1/bluem). All endpoints require a bearer API key and HTTPS. Every response carries an X-Request-Id header, and every error body the same value as request_id: quote it to BluNotify support. Every response to an authenticated request carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset for the per-partner request limit. Quick-start guide at /docs; full reference at /docs_adv. Versioning: after launch /v1 only gains things (new routes, optional parameters, fields and error codes), so a client must ignore fields it does not recognise. Anything to be removed is deprecated first, with at least six months before its sunset; a response that uses it carries Deprecation, Sunset and Link (rel="deprecation") headers, and the operation is marked deprecated here. Changes are listed at /changelog. Every /v1 answer carries Cache-Control: no-store. Bodies are JSON objects sent as application/json (415 otherwise). A create answers 201 with Location, an accepted command 202, and an update or removal with nothing to return 204.

This page is generated from the API's OpenAPI description, so it is exactly what the API documents. The guide explains the concepts behind it: scope, permissions, rate limits, and the integrator notes for BluEM meters.

Account

Your key, its permissions and its rate limits.

get /v1/account

Partner configuration

Example request

curl -X GET "https://api.blunotify.com/v1/account" \
  -H "Authorization: Bearer $KEY"

Responses

200 Partner name, permissions, allowed channels, and rate limits.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
partner always object
partner.name always string
partner.key_prefix always string
partner.permissions always object

Every permission, granted or not. A 403 names a missing one by these keys (required_permission).

partner.permissions.raw_commands always boolean
partner.permissions.predefined_commands always boolean
partner.permissions.state_read always boolean
partner.permissions.health_read always boolean
partner.permissions.automation_control always boolean
partner.permissions.relaxed_caps always boolean
partner.permissions.device_provisioning always boolean
partner.permissions.device_removal always boolean
partner.permissions.system_read always boolean
partner.permissions.system_control always boolean
partner.permissions.system_destructive always boolean
partner.permissions.file_upload always boolean
partner.permissions.file_delete always boolean
partner.permissions.raw_mqtt_devices always boolean
partner.permissions.raw_mqtt_global always boolean
partner.permissions.access_all_devices always boolean
partner.permissions.config_read always boolean
partner.permissions.config_write always boolean
partner.permissions.profile_manage always boolean
partner.permissions.energy_reset always boolean
partner.permissions.energy_read always boolean
partner.permissions.signal_manage always boolean
partner.allowed_channels always array of string
partner.rate_limits always object
partner.rate_limits.requests_per_min integer
partner.rate_limits.device_commands_per_min integer
{
  "partner": {
    "name": "string",
    "key_prefix": "string",
    "permissions": {
      "raw_commands": true,
      "predefined_commands": true,
      "state_read": true,
      "health_read": true,
      "automation_control": true,
      "relaxed_caps": true,
      "device_provisioning": true,
      "device_removal": true,
      "system_read": true,
      "system_control": true,
      "system_destructive": true,
      "file_upload": true,
      "file_delete": true,
      "raw_mqtt_devices": true,
      "raw_mqtt_global": true,
      "access_all_devices": true,
      "config_read": true,
      "config_write": true,
      "profile_manage": true,
      "energy_reset": true,
      "energy_read": true,
      "signal_manage": true
    },
    "allowed_channels": [
      "string"
    ],
    "rate_limits": {
      "requests_per_min": 0,
      "device_commands_per_min": 0
    }
  }
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}

Devices

The devices in your scope, whatever their product: listing, provisioning, removal, state and health.

get /v1/devices

List devices in scope

The devices in your scope, in device-id order. ?product= narrows the list to one product's devices ('blunotify' or 'bluem'); an unknown value is a 400, never an empty list.

Parameters

NameInTypeDescription
product querystring: blunotify, bluem

Only this product's devices.

Example request

curl -X GET "https://api.blunotify.com/v1/devices" \
  -H "Authorization: Bearer $KEY"

Responses

200 Devices in the partner's scope.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
devices always array of object
devices[].device_id always string
devices[].name always string or null
devices[].product always string or null

The product the device was provisioned as: 'blunotify' (notification light) or 'bluem' (energy monitor). Null only for a device of a product this server does not know.

devices[].channels array of string

The channels the partner may command. Only on devices of a product that has named channels (BluNotify); absent on a BluEM meter.

devices[].automation_enabled boolean

BluNotify devices only: whether temperature automation drives the light (PUT .../automation).

devices[].status string or null

With the health-read permission only.

devices[].status_changed_at string (date-time) or null

With the health-read permission only.

devices[].last_seen_at string (date-time) or null

With the health-read permission only.

{
  "devices": [
    {
      "device_id": "string",
      "name": "string",
      "product": "string",
      "channels": [
        "string"
      ],
      "automation_enabled": true,
      "status": "string",
      "status_changed_at": "2026-09-30T14:00:00+00:00",
      "last_seen_at": "2026-09-30T14:00:00+00:00"
    }
  ]
}
400 Unknown product.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}

Get one device in scope

Parameters

NameInTypeDescription
device_id required pathstring

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID" \
  -H "Authorization: Bearer $KEY"

Responses

200 Device detail.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id always string
name always string or null
product always string or null

The product the device was provisioned as: 'blunotify' (notification light) or 'bluem' (energy monitor). Null only for a device of a product this server does not know.

channels array of string

The channels the partner may command. Only on devices of a product that has named channels (BluNotify); absent on a BluEM meter.

automation_enabled boolean

BluNotify devices only: whether temperature automation drives the light (PUT .../automation).

status string or null

With the health-read permission only.

status_changed_at string (date-time) or null

With the health-read permission only.

last_seen_at string (date-time) or null

With the health-read permission only.

{
  "device_id": "string",
  "name": "string",
  "product": "string",
  "channels": [
    "string"
  ],
  "automation_enabled": true,
  "status": "string",
  "status_changed_at": "2026-09-30T14:00:00+00:00",
  "last_seen_at": "2026-09-30T14:00:00+00:00"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Outside scope or not found.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/devices/{device_id}

Provision a new device (derived-v1)

Registers a new device under the firmware's derived-v1 credential contract and places it in the partner's scope. Requires the device-provisioning permission. The device ID (path parameter) is the ESP32 base MAC as exactly 12 uppercase hexadecimal characters (no separators); it is the MQTT username, the HKDF salt, and the firmware device ID, and must not already be in use. credential_source selects what you provide (default generate): generate — the server mints a 16-character root secret, derives the 32-character MQTT broker password from it (HKDF-SHA256), and returns the root secret exactly once as password (the eFuse root secret, NOT the MQTT password); root_secret — you supply the device's existing eFuse secret and the server derives from it, which is how you RE-ENROLL a device whose eFuse is already burned (minting a new secret would derive a password the device can never produce); mqtt_password — you supply the already-derived 32-character lowercase-hex broker password verbatim and the server performs no derivation. A supplied credential is never echoed back, and the root secret is never stored server-side in any mode — it lives only in the device eFuse and the provisioning helper's own record. org placement: a partner with access_all_devices may optionally name an existing ROOT organization (omitted, the device lands in the root Staging / Inventory node); an org-scoped partner (no access_all_devices) MUST supply org naming a node INSIDE its own granted subtrees — omitted is 400 validation_error, a name that is unknown or outside scope is 400 "unknown organization '<org>'" (the two are indistinguishable, so the response never confirms an organization outside the partner's sight), and a name shared by two or more in-scope nodes is 400 "organization '<org>' is ambiguous: N organizations in your scope share that name".

Parameters

NameInTypeDescription
device_id required pathstring
Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
credential_format required string: derived-v1
credential_source string: generate, root_secret, mqtt_password

What you are providing. Only the field matching this value may be sent; supplying the others is rejected.

root_secret string

Required when credential_source is 'root_secret': the device's existing eFuse secret, 16 printable-ASCII characters with no whitespace.

mqtt_password string

Required when credential_source is 'mqtt_password': the already-derived broker password, 32 lowercase hexadecimal characters.

product string

Which device product this is. Currently 'blunotify' or 'bluem'. It selects the HKDF label the MQTT password is derived with ('<product>/mqtt/v1'), the MQTT topic root the device uses ('<product>/<DEV_ID>/...'), and the broker ACL subtree its credential is granted. Omit it and you get 'blunotify', so older tooling keeps working; an unrecognised value is rejected rather than defaulted, because a device burned against one label can never produce the password derived from another. Echoed back in the response — check it before burning an eFuse.

name string

Optional display name.

org string

Where to place the device; provisioning never creates org nodes. Optional for an access_all_devices partner (must then name an existing ROOT organization; omitted lands the device in Staging / Inventory). REQUIRED for an org-scoped partner (no access_all_devices), and must name a node inside that partner's own granted subtrees — see the operation description for the exact 400s this produces.

Example request

curl -X POST "https://api.blunotify.com/v1/devices/DEVICE_ID" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"credential_format": "derived-v1", "credential_source": "generate", "root_secret": "string", "mqtt_password": "string", "product": "bluem", "name": "string", "org": "string"}'

Responses

201 Provisioned. device_id, credential_format, product, password (the root secret, only when credential_source was generate), and name and org…

Provisioned. device_id, credential_format, product, password (the root secret, only when credential_source was generate), and name and org when given. Location names the device.

HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

Location

The resource created, or the one to poll for an outcome.

FieldTypeDescription
device_id always string
credential_format always string: derived-v1
product always string
password string

The 16-character root secret; only when the server generated it.

name string
org string
{
  "device_id": "string",
  "credential_format": "derived-v1",
  "product": "string",
  "password": "string",
  "name": "string",
  "org": "string"
}
400 Invalid device_id, missing/unsupported credential_format, unknown product, invalid credential_source or supplied credential material,…

Invalid device_id, missing/unsupported credential_format, unknown product, invalid credential_source or supplied credential material, invalid name/org, or unknown organization. Nothing is created on any of these. Or a body that is not valid JSON, or not a JSON object.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Device provisioning not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 device_id is already in use (existing devices are never modified, rotated, or disclosed). Or code idempotency_in_progress: a request with…

device_id is already in use (existing devices are never modified, rotated, or disclosed). Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Broker provisioning or DB commit failed; nothing was persisted and any partial broker credential was rolled back.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
delete /v1/devices/{device_id}

Remove a device

Cuts the device's broker credential, then deletes its record (cascades automation suspensions and sensor attachments). Requires the device-removal permission. Applies to any device in the partner's scope, not only ones it provisioned. DESTRUCTIVE TIER: confirm must equal the device id (as a query parameter or in a JSON body) or the call is refused with 409, and each removal spends a slot from the same per-partner per-hour destructive-operation ceiling as factory_reset.

Parameters

NameInTypeDescription
device_id required pathstring
confirm querystring

Must equal the device id. Required unless the same field is supplied in a JSON request body.

Request body (JSON)

FieldTypeDescription
confirm string

Must equal the device id.

Example request

curl -X DELETE "https://api.blunotify.com/v1/devices/DEVICE_ID" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm": "string"}'

Responses

204 Removed. No body.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Device removal not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Outside scope or not found.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 Missing or mismatched confirm (code confirmation_required); nothing was deleted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 Destructive-operation ceiling reached (Retry-After header). A rate limit was reached: the per-partner request limit (RateLimit-Remaining is…

Destructive-operation ceiling reached (Retry-After header). A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Broker removal failed; the device record was not deleted (retryable).
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}/state

Current device state

Requires the state-read permission. Returns the latest command per permitted channel with a decoded label.

Parameters

NameInTypeDescription
device_id required pathstring

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID/state" \
  -H "Authorization: Bearer $KEY"

Responses

200 Per-channel state.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 State access not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}/health

Device health

Requires the health-read permission.

Parameters

NameInTypeDescription
device_id required pathstring
hours queryinteger
limit queryinteger

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID/health" \
  -H "Authorization: Bearer $KEY"

Responses

200 The connectivity fields and recent health telemetry.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id always string
status string or null: connected, disconnected, None
status_changed_at string (date-time) or null

When status last changed.

last_seen_at string (date-time) or null

When the device last sent anything itself.

telemetry always array of object
telemetry[].created_at string (date-time)
telemetry[].topic string
telemetry[].payload string
{
  "device_id": "string",
  "status": "connected",
  "status_changed_at": "2026-09-30T14:00:00+00:00",
  "last_seen_at": "2026-09-30T14:00:00+00:00",
  "telemetry": [
    {
      "created_at": "2026-09-30T14:00:00+00:00",
      "topic": "string",
      "payload": "string"
    }
  ]
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Health access not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}

BluEM energy monitors

A meter's interval records, summary, consumption and live reading, and its two commands.

get /v1/bluem/devices/{device_id}/intervals

Interval records over a time range (BluEM)

Requires the energy-read permission (energy_read). Two ways of asking. BY START TIME (start/end): records come back oldest first, ordered by (start, stream id, sequence number) and cursor-paginated by that key; a cursor carries its window, so the next page is cut from the window the first page resolved, and start, end and unknown_time may be left out beside it (sent, each must equal the first request's value, or the call is a 400); pass unknown_time=true to list records with no determinable start instead. BY ARRIVAL (received_since, then cursor): every record of the meter in the order the server stored it, late records included; next_cursor is always present and is the bookmark to keep; with a cursor, received_since is ignored; start, end and unknown_time cannot be combined with it (400). A bookmark answered 400 invalid 'cursor' belongs to a database history from before a restore: start again from received_since and de-duplicate by sid and seq. Meters are tested before they ship, and the records a meter stored during the test may be cleared: cleared records are no longer listed in either mode, records from before the clear are not stored if they arrive later, and a bookmark stays valid. Each record's current_a, power_w and energy_wh are corrected for the meter's CT turns (divided by its ct_turns); voltage_v, frequency_hz and power_factor are as measured.

Parameters

NameInTypeDescription
device_id required pathstring

The BluEM device id.

start querystring

ISO-8601 or epoch seconds; defaults to 24h before end. Beside a range-mode cursor: may be left out, and must otherwise equal the first request's.

end querystring

ISO-8601 or epoch seconds; defaults to now. Beside a range-mode cursor: may be left out, and must otherwise equal the first request's.

received_since querystring

ISO-8601 or epoch seconds. Selects the arrival feed: records the server received at or after this instant, in the order it stored them. Ignored when a cursor is sent.

limit queryinteger

Default 200, max 1000.

cursor querystring

A previous answer's next_cursor, exactly as received. Cursors are URL-safe ([A-Za-z0-9_-]) and need no encoding.

unknown_time queryboolean

By start time only: list the records that have no start time.

Example request

curl -X GET "https://api.blunotify.com/v1/bluem/devices/DEVICE_ID/intervals" \
  -H "Authorization: Bearer $KEY"

Responses

200 A page of interval records. By start time: device_id, unknown_time, window, records, has_more, next_cursor (null on the last page). By…

A page of interval records. By start time: device_id, unknown_time, window, records, has_more, next_cursor (null on the last page). By arrival: device_id, mode, received_since, records, has_more, next_cursor (never null).

HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id always string
unknown_time boolean
window object

The half-open window [start, end) the answer covers.

window.start always string (date-time)
window.end always string (date-time)
records always array of object
records[].seq integer
records[].sid string

Stream id, 8 hex characters.

records[].start_at string (date-time) or null

When the interval began; null when the meter could not know (time_quality unknown).

records[].time_quality string: device, derived, unknown
records[].duration_s integer or null
records[].reads object
records[].reads.n integer or null
records[].reads.err integer or null
records[].voltage_v object or null

Voltage, in volts (V), as measured.

records[].voltage_v.avg number
records[].voltage_v.min number
records[].voltage_v.max number
records[].current_a object or null

Current, in amperes (A): the real load, already divided by ct_turns.

records[].current_a.avg number
records[].current_a.min number
records[].current_a.max number
records[].power_w object or null

Real (active) power, in watts (W): the real load, already divided by ct_turns.

records[].power_w.avg number
records[].power_w.min number
records[].power_w.max number
records[].frequency_hz object or null

Mains frequency, in hertz (Hz), as measured.

records[].frequency_hz.avg number
records[].frequency_hz.min number
records[].frequency_hz.max number
records[].power_factor object or null

Power factor, without unit (0 to 1), as measured.

records[].power_factor.avg number
records[].power_factor.min number
records[].power_factor.max number
records[].energy_wh object or null

The meter's lifetime energy counter, in watt-hours (Wh), at the record's first and last good read, divided by ct_turns (so fractional when ct_turns > 1).

records[].energy_wh.first number or null
records[].energy_wh.last number or null
records[].ct_turns integer or null

CT turns the record was measured under (firmware emCtTurns; 1 when the record omitted it). a, w and energy_wh are already divided by it. null on a quarantined record whose ct was unusable.

records[].alarm_reads integer or null
records[].flags object
records[].flags.partial boolean
records[].flags.energy_reset boolean
records[].flags.meter_reconfigured boolean
records[].quarantined boolean
records[].quarantine_reason string or null
records[].received_at string (date-time)

When the server took the record off the network. Much later than start for a record the meter stored through an outage.

next_cursor always string or null

URL-safe. By start time: null on the last page. By arrival: never null; the bookmark to keep.

mode string: received

Arrival feed only.

received_since string (date-time) or null

Arrival feed only: the instant applied, or null on a cursor call.

has_more always boolean

Both modes. By start time: true exactly when next_cursor is set. By arrival: call again at once with next_cursor.

{
  "device_id": "EM123",
  "unknown_time": false,
  "window": {
    "start": "2026-09-24T12:00:00+00:00",
    "end": "2026-09-25T12:00:00+00:00"
  },
  "records": [
    {
      "seq": 118,
      "sid": "1a2b3c4d",
      "start_at": "2026-09-25T11:45:00+00:00",
      "time_quality": "device",
      "duration_s": 900,
      "reads": {
        "n": 900,
        "err": 0
      },
      "voltage_v": {
        "avg": 229.83,
        "min": 228.1,
        "max": 231.4
      },
      "current_a": {
        "avg": 1.203,
        "min": 0.9,
        "max": 1.6
      },
      "power_w": {
        "avg": 276.1,
        "min": 205.0,
        "max": 370.0
      },
      "frequency_hz": {
        "avg": 50.01,
        "min": 49.9,
        "max": 50.1
      },
      "power_factor": {
        "avg": 0.947,
        "min": 0.9,
        "max": 0.99
      },
      "energy_wh": {
        "first": 100020.0,
        "last": 100089.0
      },
      "ct_turns": 1,
      "alarm_reads": 0,
      "flags": {
        "partial": false,
        "energy_reset": false,
        "meter_reconfigured": false
      },
      "quarantined": false,
      "quarantine_reason": null,
      "received_at": "2026-09-25T12:00:01.532+00:00"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
400 Invalid start/end/received_since/cursor, the two ways of asking mixed, or the range exceeds the cap.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Energy data access not permitted (required_permission energy_read).
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope, unknown, or not a BluEM device.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/bluem/devices/{device_id}/summary

Statistics and consumption over a window (BluEM)

Requires the energy-read permission (energy_read). Adds up exactly the records GET .../intervals lists by start time for the same start and end (records with no start time are in none). For voltage_v, current_a, power_w, frequency_hz and power_factor, over the records that carry the quantity (not quarantined, reads.n > 0, an average, a positive duration_s): avg is duration-weighted, min and max are the records' own extremes, rounded to 6 places; null, never zeros, when no record carries it. power_factor counts only records with a load (see its schema). energy_wh is the total GET .../energy gives for the same window, or null with no counter reading. coverage counts the window's records, the quarantined ones, and the seconds the rest cover. A summary of a past window can change when late records arrive.

Parameters

NameInTypeDescription
device_id required pathstring

The BluEM device id.

start querystring

ISO-8601 or epoch seconds; defaults to 24h before end.

end querystring

ISO-8601 or epoch seconds; defaults to now.

Example request

curl -X GET "https://api.blunotify.com/v1/bluem/devices/DEVICE_ID/summary" \
  -H "Authorization: Bearer $KEY"

Responses

200 The window's statistics.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id always string
window always object

The half-open window [start, end) the answer covers.

window.start always string (date-time)
window.end always string (date-time)
coverage always object
coverage.records always integer

Records with a start in the window.

coverage.quarantined always integer

Of those, quarantined.

coverage.seconds always integer

Summed duration_s of the ones not quarantined.

voltage_v always object or null

Voltage, in volts (V), as measured.

voltage_v.avg always number

Duration-weighted: sum(avg x duration_s) / sum(duration_s).

voltage_v.min always number

The lowest record min.

voltage_v.max always number

The highest record max.

voltage_v.intervals always integer

Records that carry the quantity.

voltage_v.seconds always integer

Their summed duration_s.

current_a always object or null

Current, in amperes (A): the real load, already divided by ct_turns.

current_a.avg always number

Duration-weighted: sum(avg x duration_s) / sum(duration_s).

current_a.min always number

The lowest record min.

current_a.max always number

The highest record max.

current_a.intervals always integer

Records that carry the quantity.

current_a.seconds always integer

Their summed duration_s.

power_w always object or null

Real (active) power, in watts (W): the real load, already divided by ct_turns.

power_w.avg always number

Duration-weighted: sum(avg x duration_s) / sum(duration_s).

power_w.min always number

The lowest record min.

power_w.max always number

The highest record max.

power_w.intervals always integer

Records that carry the quantity.

power_w.seconds always integer

Their summed duration_s.

frequency_hz always object or null

Mains frequency, in hertz (Hz), as measured.

frequency_hz.avg always number

Duration-weighted: sum(avg x duration_s) / sum(duration_s).

frequency_hz.min always number

The lowest record min.

frequency_hz.max always number

The highest record max.

frequency_hz.intervals always integer

Records that carry the quantity.

frequency_hz.seconds always integer

Their summed duration_s.

power_factor always object or null

Power factor, without unit (0 to 1), as measured. Over the records with a load only: those whose highest power before CT correction (power_w.max x ct_turns) reached 5 W. With nothing drawing, the meter reports a placeholder, not a ratio. A duration-weighted average of the records' own averages, not an energy-weighted power factor.

power_factor.avg always number

Duration-weighted: sum(avg x duration_s) / sum(duration_s).

power_factor.min always number

The lowest record min.

power_factor.max always number

The highest record max.

power_factor.intervals always integer

Records that carry the quantity.

power_factor.seconds always integer

Their summed duration_s.

energy_wh always object or null

Null when no record carried a counter reading.

energy_wh.total always number

Consumption over the window, in watt-hours (Wh): the total GET .../energy gives for the same window.

energy_wh.resets always integer

Energy-counter resets seen in the window.

energy_wh.intervals always integer

Records the consumption is summed over.

{
  "device_id": "7C9EBD91235C",
  "window": {
    "start": "2026-09-29T14:00:00+00:00",
    "end": "2026-09-30T14:00:00+00:00"
  },
  "coverage": {
    "records": 96,
    "quarantined": 0,
    "seconds": 86391
  },
  "voltage_v": {
    "avg": 123.077838,
    "min": 119.6,
    "max": 125.3,
    "intervals": 96,
    "seconds": 86391
  },
  "current_a": {
    "avg": 0.336163,
    "min": 0.0,
    "max": 6.9894,
    "intervals": 96,
    "seconds": 86391
  },
  "power_w": {
    "avg": 31.99751,
    "min": 0.0,
    "max": 764.92,
    "intervals": 96,
    "seconds": 86391
  },
  "frequency_hz": {
    "avg": 59.967292,
    "min": 59.9,
    "max": 60.0,
    "intervals": 96,
    "seconds": 86391
  },
  "power_factor": {
    "avg": 0.693137,
    "min": 0.0,
    "max": 0.96,
    "intervals": 55,
    "seconds": 49493
  },
  "energy_wh": {
    "total": 769.0,
    "resets": 0,
    "intervals": 96
  }
}
400 Invalid start/end, or the range exceeds a year.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Energy data access not permitted (required_permission energy_read).
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope, unknown, or not a BluEM device.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/bluem/devices/{device_id}/energy

Consumption per bucket and total (BluEM)

Requires the energy-read permission (energy_read). bucket is 'hour' or 'day' (default 'hour'). Buckets are always exactly the requested width, never silently coarser: a window the bucket cannot cover in under 2000 buckets is a 400 validation_error, so an 'hour'-bucketed range must span fewer than 2000 hours (83 days) — use bucket=day beyond that. total is null, not zero, when no record in the window carried a counter reading. Consumption is in watt-hours (Wh), corrected for CT turns like a record's energy_wh; a change of emCtTurns is never reported as a reset (the interval after it counts its own first-to-last reading). With tz the buckets are local days or hours of that zone; an unknown zone is a 400.

Parameters

NameInTypeDescription
device_id required pathstring

The BluEM device id.

bucket querystring: hour, day
start querystring
end querystring
tz querystring

An IANA zone name, e.g. America/New_York. Buckets are then that zone's local days or hours (a day at a daylight-saving change is 23 or 25 hours), each point's t is written in that zone with its offset, and the answer echoes tz. Absent: UTC buckets, exactly as before. Nothing is stored.

Example request

curl -X GET "https://api.blunotify.com/v1/bluem/devices/DEVICE_ID/energy" \
  -H "Authorization: Bearer $KEY"

Responses

200 Bucketed consumption points plus the window total; tz is echoed when it was sent.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id always string
bucket always string: hour, day
window always object

The half-open window [start, end) the answer covers.

window.start always string (date-time)
window.end always string (date-time)
points always array of object
points[].start_at always string (date-time)

The bucket's first instant (with tz, in that zone).

points[].energy_wh always number

Consumption in the bucket, in watt-hours (Wh).

points[].reset always boolean

The counter was reset in this bucket.

total always object or null
total.energy_wh always number

Consumption over the window, in watt-hours (Wh).

total.resets always integer

Energy-counter resets seen in the window.

total.intervals always integer

Records the consumption is summed over.

total.buckets always integer

Buckets with a reading.

tz string

Echoed when sent.

{
  "device_id": "EM123",
  "bucket": "day",
  "window": {
    "start": "2026-09-28T00:00:00+00:00",
    "end": "2026-09-30T00:00:00+00:00"
  },
  "points": [
    {
      "start_at": "2026-09-28T00:00:00+00:00",
      "energy_wh": 812.4,
      "reset": false
    },
    {
      "start_at": "2026-09-29T00:00:00+00:00",
      "energy_wh": 744.1,
      "reset": false
    }
  ],
  "total": {
    "energy_wh": 1556.5,
    "resets": 0,
    "intervals": 192,
    "buckets": 2
  }
}
400 Invalid bucket or start/end, the range exceeds the cap, or the bucket cannot cover the window in under 2000 buckets (an 'hour'-bucketed…

Invalid bucket or start/end, the range exceeds the cap, or the bucket cannot cover the window in under 2000 buckets (an 'hour'-bucketed range of 2000 hours / ~83 days and up), or tz is not a known zone.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Energy data access not permitted (required_permission energy_read).
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope, unknown, or not a BluEM device.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/bluem/devices/{device_id}/report

Close the interval in progress now (cmd/report)

Requires system control. Fire-and-forget (202); the record carries a partial flag and reaches GET .../intervals a few seconds later. Not a refresh: every call closes the interval in progress and stores a short record. For what the meter reads now, use POST .../sample, which stores nothing.

Parameters

NameInTypeDescription
device_id required pathstring

The BluEM device id.

Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Example request

curl -X POST "https://api.blunotify.com/v1/bluem/devices/DEVICE_ID/report" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Responses

202 Accepted
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

FieldTypeDescription
topic string
payload string
sent_at string (date-time)
command string
key string
{
  "topic": "string",
  "payload": "string",
  "sent_at": "2026-09-30T14:00:00+00:00",
  "command": "string",
  "key": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System control not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope, unknown, or not a BluEM device.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 Code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 Rate limited. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation…

Rate limited. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Publish failed.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/bluem/devices/{device_id}/sample

Ask for a live reading (BluEM)

Requires the energy-read permission (energy_read). Asks the meter what it reads now. Closes no interval and stores no record. The server does not wait for the meter: the answer is a token to poll with GET .../sample/{token}. At most one request per meter every 10 seconds, whoever asks.

Parameters

NameInTypeDescription
device_id required pathstring

The BluEM device id.

Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Example request

curl -X POST "https://api.blunotify.com/v1/bluem/devices/DEVICE_ID/sample" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Responses

202 Sent to the meter. Location names the resource to poll.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Location

The resource created, or the one to poll for an outcome.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

FieldTypeDescription
device_id string
token string
state string: pending
requested_at string (date-time)
timeout_s integer

After this the outcome is final.

{
  "device_id": "string",
  "token": "5f3a9c1e",
  "state": "pending",
  "requested_at": "2026-09-30T14:00:00+00:00",
  "timeout_s": 30
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Energy data access not permitted (required_permission energy_read).
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope, unknown, or not a BluEM device.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 code device_offline: the meter is not connected and nothing was sent. Carries status, status_changed_at and last_seen_at. Or code…

code device_offline: the meter is not connected and nothing was sent. Carries status, status_changed_at and last_seen_at. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

status string or null
status_changed_at string (date-time) or null
last_seen_at string (date-time) or null
{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string",
  "status": "string",
  "status_changed_at": "2026-09-30T14:00:00+00:00",
  "last_seen_at": "2026-09-30T14:00:00+00:00"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 This meter was asked less than 10 seconds ago. Retry-After says how long to wait. A rate limit was reached: the per-partner request limit…

This meter was asked less than 10 seconds ago. Retry-After says how long to wait. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 code publish_failed: the broker could not be reached.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/bluem/devices/{device_id}/sample/{token}

Outcome of a live reading (BluEM)

Requires the energy-read permission (energy_read). Poll every second or two until state is no longer pending; it is final within 30 seconds. The reading is a fixed set of electrical quantities and nothing of the device's own status.

Parameters

NameInTypeDescription
device_id required pathstring

The BluEM device id.

token required pathstring

The token a POST .../sample returned.

Example request

curl -X GET "https://api.blunotify.com/v1/bluem/devices/DEVICE_ID/sample/TOKEN" \
  -H "Authorization: Bearer $KEY"

Responses

200 The state of the request.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id string
token string
state string: pending, done, failed, no_reply
reason string or null

Set when state is failed: busy, config_mode, unsupported, rejected or no_snapshot.

requested_at string (date-time)
reading object

Set when state is done.

reading.link string: ok, timeout, crc, exception, badreply, notstarted, unknown

The unit's link to its meter at its last read.

reading.answered boolean

True only when link is ok.

reading.voltage_v number or null

Voltage, in volts (V), as measured.

reading.current_a number or null

Current, in amperes (A): the real load, already divided by ct_turns.

reading.power_w number or null

Real (active) power, in watts (W): the real load, already divided by ct_turns.

reading.power_factor number or null

Power factor, without unit (0 to 1), as measured. Null when nothing is drawing power.

reading.frequency_hz number or null

Mains frequency, in hertz (Hz), as measured.

reading.ct_turns integer

The CT turns the unit is set to.

reading.age_s number or null

Seconds between the meter reading these values and its answer.

reading.taken_at string (date-time)

When the answer reached the server.

reading.took_s number

Seconds from the request to the answer, to the millisecond (for example 1.278).

{
  "device_id": "EM123",
  "token": "0a1b2c3d",
  "state": "done",
  "reason": null,
  "requested_at": "2026-09-30T14:02:09.926+00:00",
  "reading": {
    "link": "ok",
    "answered": true,
    "voltage_v": 122.0,
    "current_a": 0.6268,
    "power_w": 58.54,
    "power_factor": 0.77,
    "frequency_hz": 60.0,
    "ct_turns": 5,
    "age_s": 0,
    "taken_at": "2026-09-30T14:02:11.204+00:00",
    "took_s": 1.278
  }
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Energy data access not permitted (required_permission energy_read).
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope, unknown, or not a BluEM device; or a token that was not issued for this device.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/bluem/devices/{device_id}/resetenergy

Zero a meter's lifetime energy counter (cmd/resetenergy)

DESTRUCTIVE AND IRREVERSIBLE. Requires the energy-reset permission (off by default, independent of every other flag) plus confirm equal to the device id exactly. Fire-and-forget (202) with the minted token; poll GET .../resetenergy/{token} for the outcome.

Parameters

NameInTypeDescription
device_id required pathstring

The BluEM device id.

Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
confirm required string

Must equal the device id exactly.

Example request

curl -X POST "https://api.blunotify.com/v1/bluem/devices/DEVICE_ID/resetenergy" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"confirm": "string"}'

Responses

202 Accepted. Location names the resource to poll.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Location

The resource created, or the one to poll for an outcome.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

FieldTypeDescription
topic string
payload string
sent_at string (date-time)
command string
key string
{
  "topic": "string",
  "payload": "string",
  "sent_at": "2026-09-30T14:00:00+00:00",
  "command": "string",
  "key": "string"
}
400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Energy reset permission not granted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope, unknown, or not a BluEM device.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 Missing or mismatched 'confirm'. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after…

Missing or mismatched 'confirm'. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 Rate limited (destructive ceiling included). A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a…

Rate limited (destructive ceiling included). A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Publish failed.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/bluem/devices/{device_id}/resetenergy/{token}

The outcome of one energy-reset request

Requires the energy-reset permission (same flag as the request). state is pending, done, duplicate or failed. A token not issued for this device is 404, like an unknown or out-of-scope device.

Parameters

NameInTypeDescription
device_id required pathstring

The BluEM device id.

token required pathstring

The token returned by the POST above.

Example request

curl -X GET "https://api.blunotify.com/v1/bluem/devices/DEVICE_ID/resetenergy/TOKEN" \
  -H "Authorization: Bearer $KEY"

Responses

200 The current outcome. pending carries requested_at and unmatched_rejection; the final states carry result, reason and at.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
state always string: pending, done, duplicate, failed
result string or null

The meter's answer, when final.

reason string or null

Why it failed, when it did.

at string (date-time)

When the meter answered, when final.

requested_at string (date-time)

Pending only.

unmatched_rejection boolean

Pending only: the meter has logged a rejection that names no token since the request, which may or may not be this one's answer.

{
  "state": "pending",
  "result": "string",
  "reason": "string",
  "at": "2026-09-30T14:00:00+00:00",
  "requested_at": "2026-09-30T14:00:00+00:00",
  "unmatched_rejection": true
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Energy reset permission not granted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device or token not found.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}

BluNotify lights

Commands and effects on a light's channels, and its temperature automation.

get /v1/commands

List predefined commands available to the partner

Requires the predefined-commands permission. Includes each parameter's effective min/max/default.

Example request

curl -X GET "https://api.blunotify.com/v1/commands" \
  -H "Authorization: Bearer $KEY"

Responses

200 Command list with parameter bounds.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Predefined commands not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/devices/{device_id}/commands

Send a raw command

Requires the raw-command permission and channel access. Availability caps apply unless the partner also has the relaxed-caps permission.

Parameters

NameInTypeDescription
device_id required pathstring
Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
channel required string: light1, light2, light3, sound, contact
payload required string

Example request

curl -X POST "https://api.blunotify.com/v1/devices/DEVICE_ID/commands" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"channel": "light1", "payload": "sldff0000"}'

Responses

202 Published to the broker: the device takes it from there.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

FieldTypeDescription
channel string
payload string
command string
{
  "channel": "string",
  "payload": "string",
  "command": "string"
}
400 Invalid payload or channel. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Not permitted
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 Code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 Rate limited A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation…

Rate limited A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Publish failed
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/devices/{device_id}/channels/{channel}/effects

Trigger a predefined effect

Requires the predefined-commands permission, channel access, and that the command is granted. light2/light3 effects are auto-wrapped as repeating overlays (duration_s / interval_s).

Parameters

NameInTypeDescription
device_id required pathstring
channel required pathstring: light1, light2, light3, sound, contact
Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
command required string
params object

Example request

curl -X POST "https://api.blunotify.com/v1/devices/DEVICE_ID/channels/CHANNEL/effects" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"command": "flash_white", "params": {}}'

Responses

202 Published to the broker: the device takes it from there.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

FieldTypeDescription
channel string
payload string
command string
{
  "channel": "string",
  "payload": "string",
  "command": "string"
}
400 Invalid command or parameter. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Not permitted
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 Code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 Rate limited A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation…

Rate limited A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Publish failed
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
put /v1/devices/{device_id}/automation

Enable/disable temperature-sensor automation

Requires the automation-control permission (an independent add-on permission). Disabling stops automation from overwriting light1.

Parameters

NameInTypeDescription
device_id required pathstring

Request body (JSON)

FieldTypeDescription
enabled required boolean

Example request

curl -X PUT "https://api.blunotify.com/v1/devices/DEVICE_ID/automation" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

Responses

200 Updated: device_id and automation_enabled.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 Invalid body. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Automation-control permission not granted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}

Configuration

Device names and organization placement, sensors and alert profiles.

get /v1/org-tree

Read the organization tree (config surface)

Requires allow_config_read. Returns the granted subtrees as a forest (a granted subtree-root's parent_id is nulled); the whole tree with access_all_devices; an empty forest (not 403) when no nodes are granted.

Example request

curl -X GET "https://api.blunotify.com/v1/org-tree" \
  -H "Authorization: Bearer $KEY"

Responses

200 { nodes: [{ id, parent_id, name }] }
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 config read permission not granted
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}/config

Read a device's configuration

Requires allow_config_read. Out-of-scope devices return 404 indistinguishable from nonexistent.

Parameters

NameInTypeDescription
device_id required pathstring

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID/config" \
  -H "Authorization: Bearer $KEY"

Responses

200 { device_id, name, org_node, sensors:[{tdl_id,label,profile}], automation_enabled, alert_signal_id }
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 config read permission not granted
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not in scope or not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
patch /v1/devices/{device_id}/config

Rename a device

Requires allow_config_write. Sets the device's canonical name.

Parameters

NameInTypeDescription
device_id required pathstring

Request body (JSON)

FieldTypeDescription
name required string

Example request

curl -X PATCH "https://api.blunotify.com/v1/devices/DEVICE_ID/config" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "string"}'

Responses

204 Renamed. No body.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
put /v1/devices/{device_id}/org

Move a device to an org node

Requires allow_config_write. The target node must be within the partner's own scope (else 403 node_out_of_scope). For an org-scoped partner (no access_all_devices), org_node_id: null is ALWAYS 409 would_lose_access, since a granted subtree is the partner's only reach to the device; only a partner with access_all_devices may detach a device back to Staging / Inventory this way.

Parameters

NameInTypeDescription
device_id required pathstring

Request body (JSON)

FieldTypeDescription
org_node_id required integer or null

Example request

curl -X PUT "https://api.blunotify.com/v1/devices/DEVICE_ID/org" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"org_node_id": 0}'

Responses

204 Moved. No body.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 node_out_of_scope
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 would_lose_access
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/devices/{device_id}/sensors

Attach a sensor to a device

Requires allow_config_write. Incremental attach; 409 if already attached. profile_id must be global or partner-owned (else 404).

Parameters

NameInTypeDescription
device_id required pathstring
Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
tdl_id required string
profile_id integer or null

Example request

curl -X POST "https://api.blunotify.com/v1/devices/DEVICE_ID/sensors" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"tdl_id": "string", "profile_id": 0}'

Responses

201 Attached. Location names the attachment (PUT and DELETE address it).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

Location

The resource created, or the one to poll for an outcome.

FieldTypeDescription
device_id string
tdl_id string
profile_id integer or null
{
  "device_id": "string",
  "tdl_id": "string",
  "profile_id": 0
}
400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device or profile not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 already_attached. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
put /v1/devices/{device_id}/sensors/{tdl_id}

Set a sensor's alert profile

Requires allow_config_write. profile_id null clears to Default; a non-visible profile returns 404.

Parameters

NameInTypeDescription
device_id required pathstring
tdl_id required pathstring

Request body (JSON)

FieldTypeDescription
profile_id integer or null

Example request

curl -X PUT "https://api.blunotify.com/v1/devices/DEVICE_ID/sensors/TDL_ID" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"profile_id": 0}'

Responses

204 Updated. No body.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not attached / profile not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
delete /v1/devices/{device_id}/sensors/{tdl_id}

Detach a sensor from a device

Requires allow_config_write. 404 if not attached.

Parameters

NameInTypeDescription
device_id required pathstring
tdl_id required pathstring

Example request

curl -X DELETE "https://api.blunotify.com/v1/devices/DEVICE_ID/sensors/TDL_ID" \
  -H "Authorization: Bearer $KEY"

Responses

204 Detached. No body.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not attached
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/profiles

List alert profiles

Requires allow_config_read. Returns global + own partner-owned profiles.

Example request

curl -X GET "https://api.blunotify.com/v1/profiles" \
  -H "Authorization: Bearer $KEY"

Responses

200 { profiles: [{ id, name, scope, version, description, rules }] }
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/profiles

Create a partner-owned alert profile

Requires allow_profile_manage. Body is validated by the rule engine. Cap 50 profiles per partner (403 limit_reached).

Parameters

NameInTypeDescription
Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
name required string
rules required array of object
default string

Example request

curl -X POST "https://api.blunotify.com/v1/profiles" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "string", "rules": [{}], "default": "string"}'

Responses

201 Created. Location names the profile.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

Location

The resource created, or the one to poll for an outcome.

FieldTypeDescription
id always integer
{
  "id": 0
}
400 validation_error. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied / limit_reached
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 name_taken. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
put /v1/profiles/{profile_id}

Update a partner-owned alert profile

Requires allow_profile_manage. Own rows only (global/system/other → 404). Bumps version + records a revision.

Parameters

NameInTypeDescription
profile_id required pathinteger

Request body (JSON)

FieldTypeDescription
name string
rules array of object
default string

Example request

curl -X PUT "https://api.blunotify.com/v1/profiles/PROFILE_ID" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "string", "rules": [{}], "default": "string"}'

Responses

200 Updated: the profile id and its new version.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
id always integer
version always integer
{
  "id": 0,
  "version": 0
}
400 validation_error. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 name_taken
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
delete /v1/profiles/{profile_id}

Delete a partner-owned alert profile

Requires allow_profile_manage. Own rows only; 409 in_use with a count when still referenced (no force).

Parameters

NameInTypeDescription
profile_id required pathinteger

Example request

curl -X DELETE "https://api.blunotify.com/v1/profiles/PROFILE_ID" \
  -H "Authorization: Bearer $KEY"

Responses

204 Deleted. No body.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 in_use
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/alert-signals

List alert signals

Requires allow_config_read. Returns the global signals (including the default) and your own partner-owned signals.

Example request

curl -X GET "https://api.blunotify.com/v1/alert-signals" \
  -H "Authorization: Bearer $KEY"

Responses

200 { alert_signals: [AlertSignal] }
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
alert_signals always array of object
alert_signals[].id always integer
alert_signals[].name always string
alert_signals[].scope always string: global, partner
alert_signals[].is_default always boolean

The default signal: what a light with no signal of its own shows.

alert_signals[].version always integer
alert_signals[].description string or null
alert_signals[].entries always object

One entry per alert state group. NO_DATA covers NO_SENSORS, INSUFFICENT_DATA, STALE_DATA and UNKNOWN.

alert_signals[].entries.CLEAR always object
alert_signals[].entries.CLEAR.light always object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

alert_signals[].entries.CLEAR.light.type always string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
alert_signals[].entries.ADVISORY always object
alert_signals[].entries.ADVISORY.light always object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

alert_signals[].entries.ADVISORY.light.type always string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
alert_signals[].entries.WARNING always object
alert_signals[].entries.WARNING.light always object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

alert_signals[].entries.WARNING.light.type always string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
alert_signals[].entries.NO_DATA always object
alert_signals[].entries.NO_DATA.light always object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

alert_signals[].entries.NO_DATA.light.type always string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
{
  "alert_signals": [
    {
      "id": 0,
      "name": "string",
      "scope": "global",
      "is_default": true,
      "version": 0,
      "description": "string",
      "entries": {
        "CLEAR": {
          "light": {
            "type": "Solid"
          }
        },
        "ADVISORY": {
          "light": {
            "type": "Solid"
          }
        },
        "WARNING": {
          "light": {
            "type": "Solid"
          }
        },
        "NO_DATA": {
          "light": {
            "type": "Solid"
          }
        }
      }
    }
  ]
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/alert-signals

Create a partner-owned alert signal

Requires allow_signal_manage. Every entry is validated as the device would run it, with the availability caps (no light period under 100 ms). Cap 50 signals per partner (403 limit_reached).

Parameters

NameInTypeDescription
Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
name required string
description string
entries required object

One entry per alert state group. NO_DATA covers NO_SENSORS, INSUFFICENT_DATA, STALE_DATA and UNKNOWN.

entries.CLEAR required object
entries.CLEAR.light required object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

entries.CLEAR.light.type required string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
entries.ADVISORY required object
entries.ADVISORY.light required object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

entries.ADVISORY.light.type required string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
entries.WARNING required object
entries.WARNING.light required object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

entries.WARNING.light.type required string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
entries.NO_DATA required object
entries.NO_DATA.light required object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

entries.NO_DATA.light.type required string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase

Example request

curl -X POST "https://api.blunotify.com/v1/alert-signals" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "string", "description": "string", "entries": {"CLEAR": {"light": {"type": "Solid"}}, "ADVISORY": {"light": {"type": "Solid"}}, "WARNING": {"light": {"type": "Solid"}}, "NO_DATA": {"light": {"type": "Solid"}}}}'

Responses

201 Created. Location names the signal.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

Location

The resource created, or the one to poll for an outcome.

FieldTypeDescription
id always integer
{
  "id": 0
}
400 validation_error. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied / limit_reached
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 name_taken. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
put /v1/alert-signals/{signal_id}

Update a partner-owned alert signal

Requires allow_signal_manage. Own rows only (global/default/other → 404). Send any of name, description, entries; bumps version. Lights showing it change at their next state change or within the hour.

Parameters

NameInTypeDescription
signal_id required pathinteger

Request body (JSON)

FieldTypeDescription
name string
description string
entries object

One entry per alert state group. NO_DATA covers NO_SENSORS, INSUFFICENT_DATA, STALE_DATA and UNKNOWN.

entries.CLEAR required object
entries.CLEAR.light required object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

entries.CLEAR.light.type required string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
entries.ADVISORY required object
entries.ADVISORY.light required object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

entries.ADVISORY.light.type required string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
entries.WARNING required object
entries.WARNING.light required object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

entries.WARNING.light.type required string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase
entries.NO_DATA required object
entries.NO_DATA.light required object

A light effect: {type, ...fields}. type is Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid or BluChase; the fields are the ones the Effect Designer posts (see the advanced docs).

entries.NO_DATA.light.type required string: Solid, Pulse, PulseThenSolid, Chase, ChaseThenSolid, Toggle, ToggleThenSolid, BluChase

Example request

curl -X PUT "https://api.blunotify.com/v1/alert-signals/SIGNAL_ID" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "string", "description": "string", "entries": {"CLEAR": {"light": {"type": "Solid"}}, "ADVISORY": {"light": {"type": "Solid"}}, "WARNING": {"light": {"type": "Solid"}}, "NO_DATA": {"light": {"type": "Solid"}}}}'

Responses

200 Updated: the profile id and its new version.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
id always integer
version always integer
{
  "id": 0,
  "version": 0
}
400 validation_error. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 name_taken
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
delete /v1/alert-signals/{signal_id}

Delete a partner-owned alert signal

Requires allow_signal_manage. Own rows only. Allowed while in use: the lights showing it go back to the default signal.

Parameters

NameInTypeDescription
signal_id required pathinteger

Example request

curl -X DELETE "https://api.blunotify.com/v1/alert-signals/SIGNAL_ID" \
  -H "Authorization: Bearer $KEY"

Responses

204 Deleted. No body.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
put /v1/devices/{device_id}/alert-signal

Choose a light's alert signal

Requires allow_config_write. alert_signal_id is a global or your own partner-owned signal (404 otherwise); null, or the default signal's id, means the default.

Parameters

NameInTypeDescription
device_id required pathstring

Request body (JSON)

FieldTypeDescription
alert_signal_id required integer or null

Example request

curl -X PUT "https://api.blunotify.com/v1/devices/DEVICE_ID/alert-signal" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"alert_signal_id": 0}'

Responses

200 The device and the signal it now shows (null = default).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id always string
alert_signal_id always integer or null
{
  "device_id": "string",
  "alert_signal_id": 0
}
400 A body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 denied
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Not attached / profile not found
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}

System

The device's own MQTT interface: settings, status, structured commands and the platform file server.

get /v1/system/commands

System command registry (System API)

Every system verb, its tier, parameters, whether it needs confirm, and whether the caller holds that tier. Also lists the destructive settings keys. Requires system read.

Example request

curl -X GET "https://api.blunotify.com/v1/system/commands" \
  -H "Authorization: Bearer $KEY"

Responses

200 Verb registry + dangerous settings keys.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System read not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/system/files

List platform files (System API)

Files devices download (files/ + firmware/). Requires system read.

Example request

curl -X GET "https://api.blunotify.com/v1/system/files" \
  -H "Authorization: Bearer $KEY"

Responses

200 File and firmware listings.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System read not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/system/files

Upload a platform file (System API)

Requires the file-upload permission. Extension allowlist; firmware/ accepts .bin only; size-capped. Uploads never overwrite (409 on name clash). An .mp3 uploaded into files/ is checked against what a BluNotify device can play: one that holds no MP3 (MPEG Layer III) audio is refused, and any other comes back with an audio assessment whose warnings say what would make it better (mono, 16 kHz, 48 kbps, no tags). Warnings never block.

Example request

curl -X POST "https://api.blunotify.com/v1/system/files" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)"

Responses

201 Stored: dir, name, size, sha256, and `audio` for an .mp3 in files/.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
dir always string: files, firmware
name always string
size always integer
sha256 always string
audio object

Present for an .mp3 uploaded into files/.

audio.playable always boolean

The file holds MP3 (MPEG Layer III) audio a device can play.

audio.ideal always boolean

No warnings: the file is ready for the device as it is.

audio.fixable always boolean

Converting the file to the recommended format would fix at least one warning.

audio.summary always object
audio.summary.text string

One line for people, e.g. "44.1 kHz, stereo, 192 kbps, 2 min 38 s, 3.63 MB, 37.2% of device storage".

audio.summary.format string or null

e.g. "MPEG-1 Layer III", or the real format of a file that is not MP3 ("WAV").

audio.summary.sample_rate_hz integer or null
audio.summary.channels integer or null
audio.summary.channel_mode string or null: stereo, joint_stereo, dual_channel, mono, None
audio.summary.bitrate_kbps integer or null

Average over the audio.

audio.summary.vbr boolean
audio.summary.duration_s number
audio.summary.size_bytes integer
audio.summary.device_storage_pct number

Share of a device's sound-file storage the file takes.

audio.summary.tag_bytes integer

ID3 tags, cover art included.

audio.summary.cover_art boolean
audio.summary.header_frame string or null: Xing, Info, VBRI, None
audio.issues always array of object

Most serious first.

audio.issues[].code always string

e.g. not_mp3, not_layer3, too_big, large_file, stereo, high_sample_rate, high_bitrate, large_tags, uppercase_extension, low_sample_rate, long_clip.

audio.issues[].severity always string: error, warn, info
audio.issues[].message always string

Plain-language explanation, fit to show a user.

{
  "dir": "files",
  "name": "string",
  "size": 0,
  "sha256": "string",
  "audio": {
    "playable": true,
    "ideal": true,
    "fixable": true,
    "summary": {
      "text": "string",
      "format": "string",
      "sample_rate_hz": 0,
      "channels": 0,
      "channel_mode": "stereo",
      "bitrate_kbps": 0,
      "vbr": true,
      "duration_s": 0.0,
      "size_bytes": 0,
      "device_storage_pct": 0.0,
      "tag_bytes": 0,
      "cover_art": true,
      "header_frame": "Xing"
    },
    "issues": [
      {
        "code": "string",
        "severity": "error",
        "message": "string"
      }
    ]
  }
}
400 Invalid directory, name, or extension, or an .mp3 that holds no MP3 (MPEG Layer III) audio a BluNotify device can play.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 File upload not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 A file with that name already exists.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
delete /v1/system/files

Delete a platform file (System API)

Requires the file-delete permission. Protected names are refused.

Parameters

NameInTypeDescription
dir querystring: files, firmware
name required querystring

Example request

curl -X DELETE "https://api.blunotify.com/v1/system/files?name=NAME" \
  -H "Authorization: Bearer $KEY"

Responses

204 Deleted. No body.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

400 Invalid directory or name (or protected). Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 File delete not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 File not found.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}/system/settings

Cached device settings (System API)

Latest NVS settings, fw_ver, and file_list. Requires system read.

Parameters

NameInTypeDescription
device_id required pathstring

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID/system/settings" \
  -H "Authorization: Bearer $KEY"

Responses

200 Settings, firmware version, and file list.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System read not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}/system/status

Device status snapshot & health (System API)

Status, last connection string, on-demand status snapshots, and the health heartbeat. Requires system read.

Parameters

NameInTypeDescription
device_id required pathstring

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID/system/status" \
  -H "Authorization: Bearer $KEY"

Responses

200 The connectivity fields (status, status_changed_at, last_seen_at), last_conn, status_snapshots and health.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System read not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}/system/responses

Command response feed (System API)

Recent command acknowledgements and device log lines. Page with since/limit; use server_now as the next cursor. Requires system read.

Parameters

NameInTypeDescription
device_id required pathstring
cursor querystring

A previous answer's next_cursor: the rows after it, oldest first. Wins over since.

limit queryinteger

Default 200, at most 1000.

since querystring (date-time)

The older way to page: rows at or after this instant (percent-encode the '+' of its offset). Prefer cursor.

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID/system/responses" \
  -H "Authorization: Bearer $KEY"

Responses

200 A page of rows: oldest first with a cursor. Without one, the last ten minutes (or since).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id string
rows always array of object
rows[].id integer
rows[].created_at string (date-time)
rows[].topic string
rows[].payload string
has_more always boolean

More rows are waiting now: ask again with next_cursor at once.

next_cursor always string

Always set: pass it back as cursor. With no rows it is the cursor sent, or a position at the present.

server_now string (date-time)

For the older since paging.

{
  "device_id": "string",
  "rows": [
    {
      "id": 0,
      "created_at": "2026-09-30T14:00:00+00:00",
      "topic": "string",
      "payload": "string"
    }
  ],
  "has_more": true,
  "next_cursor": "string",
  "server_now": "2026-09-30T14:00:00+00:00"
}
400 An invalid cursor or since.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System read not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
post /v1/devices/{device_id}/system/commands

Run a system verb (System API)

Control verbs need system control; destructive verbs need system destructive + confirm == device_id. Fire-and-forget (202). Call GET /v1/system/commands for the verb list and parameters.

Parameters

NameInTypeDescription
device_id required pathstring
Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
command required string
params object
confirm string

Required for destructive verbs; must equal the device id.

Example request

curl -X POST "https://api.blunotify.com/v1/devices/DEVICE_ID/system/commands" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"command": "query", "params": {}, "confirm": "string"}'

Responses

202 Accepted
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

FieldTypeDescription
topic string
payload string
sent_at string (date-time)
command string
key string
{
  "topic": "string",
  "payload": "string",
  "sent_at": "2026-09-30T14:00:00+00:00",
  "command": "string",
  "key": "string"
}
400 Unknown verb or invalid params. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Tier not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 Destructive verb missing/mismatched confirm. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running;…

Destructive verb missing/mismatched confirm. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 Rate limited. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation…

Rate limited. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Publish failed.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
put /v1/devices/{device_id}/system/settings/{key}

Write an NVS settings key (System API)

Ordinary keys need system control; connection/identity keys (mqttUser, mqttPass, devId, topicRoot, host, tlsMode, mqttPort, caCertFile) need system destructive + confirm == device_id. Fire-and-forget (202). On a BluEM device the keys emAck, pmBaud, pmSlave and emCtTurns are fixed by the meter hardware and its wiring and cannot be written: 403 forbidden, whatever the caller's permissions.

Parameters

NameInTypeDescription
device_id required pathstring
key required pathstring

Request body (JSON)

FieldTypeDescription
value required string
confirm string

Example request

curl -X PUT "https://api.blunotify.com/v1/devices/DEVICE_ID/system/settings/KEY" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": "string", "confirm": "string"}'

Responses

202 Accepted
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
topic string
payload string
sent_at string (date-time)
command string
key string
{
  "topic": "string",
  "payload": "string",
  "sent_at": "2026-09-30T14:00:00+00:00",
  "command": "string",
  "key": "string"
}
400 Invalid key or value. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 Tier not permitted, or the key is locked for this device's product.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 Dangerous key missing/mismatched confirm.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 Rate limited. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation…

Rate limited. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Publish failed.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}

File transfers

Pushing a platform file to a device as a job to poll.

post /v1/devices/{device_id}/files

Push a platform file to a device, as a transfer to poll

Requires system control (system_control); polling the transfer needs system read (system_read). Sends addfile> with a correlation token and records a durable transfer. An Idempotency-Key header makes a retry return the original transfer (200) instead of downloading twice.

Parameters

NameInTypeDescription
device_id required pathstring
Idempotency-Key headerstring

A value of your choosing (a UUID, for example) that makes a retry safe. A repeat of the same request with the same key within 24 hours gets the first response back, status and body, without the work being done again; it carries Idempotent-Replayed: true. Scoped to your account and this route. The same key with a different request is a 422 idempotency_key_reused; a repeat while the first is still running is a 409 idempotency_in_progress. Only a successful (2xx) response is kept: after a failure the same key runs the request again. Not taken by the multipart file upload, which never overwrites anyway.

Request body (JSON)

FieldTypeDescription
name required string

A file in the platform files/ directory.

dest string

Its name on the device; defaults to name.

Example request

curl -X POST "https://api.blunotify.com/v1/devices/DEVICE_ID/files" \
  -H "Authorization: Bearer $KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "string", "dest": "string"}'

Responses

200 The transfer an earlier request with the same Idempotency-Key created.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

FieldTypeDescription
transfer_id always string
device_id always string
state always string: queued, sent, succeeded, failed, timed_out
terminal always boolean

Final: stop polling.

src always string

The platform file pushed.

dest always string

Its name on the device.

sha256 always string
corr_mode string
attempts always integer
detail string or null

The device's reply, or why it timed out.

timeline always object
timeline.created_at string (date-time) or null
timeline.sent_at string (date-time) or null
timeline.ack_deadline string (date-time) or null
timeline.settled_at string (date-time) or null
note string

While sent: the device drops its MQTT session while it downloads.

device_online boolean

POST only: whether the device was connected when the push was sent.

poll_after_s always number or null

Seconds to wait before polling again; null once terminal.

{
  "transfer_id": "string",
  "device_id": "string",
  "state": "queued",
  "terminal": true,
  "src": "string",
  "dest": "string",
  "sha256": "string",
  "corr_mode": "string",
  "attempts": 0,
  "detail": "string",
  "timeline": {
    "created_at": "2026-09-30T14:00:00+00:00",
    "sent_at": "2026-09-30T14:00:00+00:00",
    "ack_deadline": "2026-09-30T14:00:00+00:00",
    "settled_at": "2026-09-30T14:00:00+00:00"
  },
  "note": "string",
  "device_online": true,
  "poll_after_s": 0.0
}
202 Sent; poll the transfer. Location names the resource to poll.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Location

The resource created, or the one to poll for an outcome.

Idempotent-Replayed

true on a response replayed for a repeated Idempotency-Key.

FieldTypeDescription
transfer_id always string
device_id always string
state always string: queued, sent, succeeded, failed, timed_out
terminal always boolean

Final: stop polling.

src always string

The platform file pushed.

dest always string

Its name on the device.

sha256 always string
corr_mode string
attempts always integer
detail string or null

The device's reply, or why it timed out.

timeline always object
timeline.created_at string (date-time) or null
timeline.sent_at string (date-time) or null
timeline.ack_deadline string (date-time) or null
timeline.settled_at string (date-time) or null
note string

While sent: the device drops its MQTT session while it downloads.

device_online boolean

POST only: whether the device was connected when the push was sent.

poll_after_s always number or null

Seconds to wait before polling again; null once terminal.

{
  "transfer_id": "string",
  "device_id": "string",
  "state": "queued",
  "terminal": true,
  "src": "string",
  "dest": "string",
  "sha256": "string",
  "corr_mode": "string",
  "attempts": 0,
  "detail": "string",
  "timeline": {
    "created_at": "2026-09-30T14:00:00+00:00",
    "sent_at": "2026-09-30T14:00:00+00:00",
    "ack_deadline": "2026-09-30T14:00:00+00:00",
    "settled_at": "2026-09-30T14:00:00+00:00"
  },
  "note": "string",
  "device_online": true,
  "poll_after_s": 0.0
}
400 Missing or invalid name, unknown file, or a type this device does not accept. Or a body that is not valid JSON, or not a JSON object.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System control not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
409 The device has no registered product. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry…

The device has no registered product. Or code idempotency_in_progress: a request with the same Idempotency-Key is still running; retry after Retry-After.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
415 Code unsupported_media_type: the body was not sent as application/json.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
422 Code idempotency_key_reused: this Idempotency-Key was used with a different request.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 Per-device system-command limit. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device…

Per-device system-command limit. A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
503 Publish failed.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}/files/transfers

This partner's transfers for a device, newest first

Requires system read (system_read). ?state= and ?since= filter; the reconciliation read after an outage.

Parameters

NameInTypeDescription
device_id required pathstring
state querystring
since querystring (date-time)
limit queryinteger

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID/files/transfers" \
  -H "Authorization: Bearer $KEY"

Responses

200 Transfers.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
device_id always string
transfers always array of object
transfers[].transfer_id always string
transfers[].device_id always string
transfers[].state always string: queued, sent, succeeded, failed, timed_out
transfers[].terminal always boolean

Final: stop polling.

transfers[].src always string

The platform file pushed.

transfers[].dest always string

Its name on the device.

transfers[].sha256 always string
transfers[].corr_mode string
transfers[].attempts always integer
transfers[].detail string or null

The device's reply, or why it timed out.

transfers[].timeline always object
transfers[].timeline.created_at string (date-time) or null
transfers[].timeline.sent_at string (date-time) or null
transfers[].timeline.ack_deadline string (date-time) or null
transfers[].timeline.settled_at string (date-time) or null
transfers[].note string

While sent: the device drops its MQTT session while it downloads.

transfers[].device_online boolean

POST only: whether the device was connected when the push was sent.

transfers[].poll_after_s always number or null

Seconds to wait before polling again; null once terminal.

{
  "device_id": "string",
  "transfers": [
    {
      "transfer_id": "string",
      "device_id": "string",
      "state": "queued",
      "terminal": true,
      "src": "string",
      "dest": "string",
      "sha256": "string",
      "corr_mode": "string",
      "attempts": 0,
      "detail": "string",
      "timeline": {
        "created_at": "2026-09-30T14:00:00+00:00",
        "sent_at": "2026-09-30T14:00:00+00:00",
        "ack_deadline": "2026-09-30T14:00:00+00:00",
        "settled_at": "2026-09-30T14:00:00+00:00"
      },
      "note": "string",
      "device_online": true,
      "poll_after_s": 0.0
    }
  ]
}
400 Invalid since.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System read not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
get /v1/devices/{device_id}/files/transfers/{transfer_id}

Poll one transfer

Requires system read (system_read). Terminal when terminal is true; until then wait poll_after_s seconds. A transfer id that is not this partner's, or not for this device, is the same 404 as an unknown one.

Parameters

NameInTypeDescription
device_id required pathstring
transfer_id required pathstring

Example request

curl -X GET "https://api.blunotify.com/v1/devices/DEVICE_ID/files/transfers/TRANSFER_ID" \
  -H "Authorization: Bearer $KEY"

Responses

200 The transfer.
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

FieldTypeDescription
transfer_id always string
device_id always string
state always string: queued, sent, succeeded, failed, timed_out
terminal always boolean

Final: stop polling.

src always string

The platform file pushed.

dest always string

Its name on the device.

sha256 always string
corr_mode string
attempts always integer
detail string or null

The device's reply, or why it timed out.

timeline always object
timeline.created_at string (date-time) or null
timeline.sent_at string (date-time) or null
timeline.ack_deadline string (date-time) or null
timeline.settled_at string (date-time) or null
note string

While sent: the device drops its MQTT session while it downloads.

device_online boolean

POST only: whether the device was connected when the push was sent.

poll_after_s always number or null

Seconds to wait before polling again; null once terminal.

{
  "transfer_id": "string",
  "device_id": "string",
  "state": "queued",
  "terminal": true,
  "src": "string",
  "dest": "string",
  "sha256": "string",
  "corr_mode": "string",
  "attempts": 0,
  "detail": "string",
  "timeline": {
    "created_at": "2026-09-30T14:00:00+00:00",
    "sent_at": "2026-09-30T14:00:00+00:00",
    "ack_deadline": "2026-09-30T14:00:00+00:00",
    "settled_at": "2026-09-30T14:00:00+00:00"
  },
  "note": "string",
  "device_online": true,
  "poll_after_s": 0.0
}
401 Missing, malformed, unknown, revoked or disabled API key (one generic answer for all of them).
HeaderDescription
X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

WWW-Authenticate

Bearer realm="BluNotify Partner API" (RFC 6750), with error="invalid_token" when a key was sent.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
403 System read not permitted.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
404 Device outside scope, or transfer not found.
FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}
429 A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the…

A rate limit was reached: the per-partner request limit (RateLimit-Remaining is then 0), or a per-device or per-operation limit of the route. Retry-After says when to retry.

HeaderDescription
Retry-After

Seconds to wait before retrying.

X-Request-Id

The id of this request, on every response. Error bodies carry the same value as request_id; quote it to BluNotify support to find the call.

RateLimit-Limit

The per-partner request limit: requests per minute.

RateLimit-Remaining

Requests the current minute still admits.

RateLimit-Reset

Seconds until the oldest request counted in the current minute leaves it, i.e. until RateLimit-Remaining next grows.

Cache-Control

Always no-store on /v1: answers are one partner's, and some carry secrets.

FieldTypeDescription
error always string
code always string
request_id always string

The same value as the X-Request-Id header.

required_permission string

On a 403 refused because the key lacks a permission the request needs: its name, as GET /v1/account lists it (for example energy_read). Permissions are checked first, so with it granted the request can still be refused for something else. Absent on a 403 no permission would lift, and never on a 404.

{
  "error": "string",
  "code": "string",
  "request_id": "string",
  "required_permission": "string"
}