All chapters
Sending data
If a device can make a web request, publish an MQTT message or send an e-mail, it can report to Huntress with no driver, SDK or custom firmware. Every door below leads to the same ingestion service, so a reading is a reading whichever way it arrived.
Overview#
| Protocol | Port | Identifies the sender by | Carries |
|---|---|---|---|
| HTTP (the Hunt API) | Your web port, normally 443 behind a proxy | A token in the URL | Readings, person events, pictures, alarm-panel messages |
| MQTT | 1883, plain TCP | The token as the MQTT user name | Readings, pictures, output state and acknowledgements |
| SMTP | 25, plain TCP | The token as the recipient address | Readings and pictures sent as mail |
| SIA DC-09 | 2000, plain TCP | The panel's account code, optionally with an AES key | Alarm-panel events |
| Contact ID | 2001, plain TCP | The panel's account code | Alarm-panel events |
| Zigbee | A USB or serial coordinator | Pairing | Sensors and outputs, see Devices |
| Edge links | Your web port (WebSocket) | A token and an edge secret | Camera video and pictures, Zigbee, HTTP relay calls |
The Hunt API#
The Hunt API is the HTTP door. Its one endpoint accepts whatever a device can easily produce.
Authentication#
There are no headers to set. The token (a GUID) in the URL identifies and authorises the sender: it is on the sensor's, person's or panel's settings page. Possession of the token is enough to report, so keep it secret; if one leaks, replace the sensor and delete the old one. Tokens are shortened to eight characters in logs, and Cookie, Authorization and proxy headers are never stored with a request.
POST /hunt/{token}#
The body is read according to its Content-Type:
| Content-Type | What Huntress does with it |
|---|---|
application/json | All fields below. The most flexible form. JSON field names are case-sensitive. |
application/x-www-form-urlencoded | The fields value, eventCode, unit, eventType, latitude, longitude and deviceInfo (names are case-insensitive). No imageData. |
text/plain | A bare number (23.4) is a reading. A single word of letters, up to 32 characters (Keepalive), is a person event. |
image/jpeg, image/png, image/webp | The body is a picture for a camera sensor. |
application/x-sia-dc03, application/x-contact-id | An alarm-panel message; the token is the panel's token. See Alarm panels. |
| No content type | Detected: JSON if the body starts with {, otherwise text, otherwise a JPEG or PNG. |
| Anything else | Stored as an unknown event with the body kept as base64 (nothing is lost, nothing is interpreted). |
ON or alarm, is read as a person event, not a reading. Posted to a sensor token it answers 404 “Person not found”. Send readings as JSON, {"value": 1}, or as a number.JSON fields#
What the body holds decides what it is, in this order: value makes it a sensor reading; otherwise eventType makes it a person event; otherwise imageData alone makes it a camera picture; otherwise it is stored as an unknown event.
| Field | Type | Meaning |
|---|---|---|
value | number | The reading. Must be a JSON number (not "1") and finite. 1 and 0 for on/off sensors. |
eventCode | string, up to 64 | Names the event this reading means, from the sensor type's list. An invalid code is ignored and the type's own classification is used. |
unit | string, up to 32 | A unit for this reading only, such as °F. A reading in a different unit than the sensor's is not compared with its limits. |
imageData | string | A base64 JPEG or PNG, up to 10 MiB decoded. With value it attaches a snapshot to the reading; alone it is a camera picture. On a person event it is accepted but not stored. |
eventType | string, up to 64 | Movement, PanicAlarm or Keepalive (any case). The person must have Pulse enabled. |
latitude, longitude | number | A position, −90 to 90 and −180 to 180. Used with eventType. |
deviceInfo | string, up to 1024 | A description of the device (model, app version). Used with eventType; shown on the person's Pulse tab. |
A temperature reading
curl -X POST https://<host>/hunt/<sensor-token> \
-H "Content-Type: application/json" \
-d '{"value": 21.5, "unit": "°C"}'Pictures#
Huntress accepts a picture as a raw body (image/*), as imageData, as an MQTT payload or as an e-mail attachment. Pictures larger than 10 MiB or 24 megapixels are refused; the rest are scaled to at most 1920 pixels wide and stored as JPEG. A picture sent to a sensor that is not a camera answers 400 “Not a visual sensor”, and a numeric value sent to a camera answers 400 “This is a visual sensor”. A picture attached to a reading that is too large is dropped, and the reading is stored without it.
A camera snapshot
curl -X POST https://<host>/hunt/<camera-token> \ -H "Content-Type: image/jpeg" \ --data-binary @snapshot.jpg
Person events#
A person reports with the Pulse key in place of a sensor token. The Pulse app does this for you; your own app or tracker can too.
| eventType | Meaning | Severity |
|---|---|---|
| Movement | The person moved; carries a position. | Info |
| Keepalive | A heartbeat: the person and the device are fine. | Info |
| PanicAlarm | The person raised an alarm. | Critical |
A panic alarm with a position
curl -X POST https://<host>/hunt/<pulse-key> \
-H "Content-Type: application/json" \
-d '{"eventType": "PanicAlarm", "latitude": 59.3293, "longitude": 18.0686}'Answers#
A stored request answers 200 with JSON that echoes what was stored. A problem answers with {"error": "…"}.
| Status | Body | Meaning |
|---|---|---|
| 200 | {sensorId, sensorName, value, timestamp} | A reading was stored. |
| 200 | {sensorId, sensorName, width, height, timestamp} | A picture was stored. |
| 200 | {personId, personName, eventType, latitude, longitude, timestamp} | A person event was stored. |
| 200 | {accepted: true, timestamp} | An unclassified or raw payload was stored as an unknown event. |
| 400 | {error} | Invalid body or field, a value for a camera, a picture for a non-camera, a picture too large. |
| 404 | {error} | Unknown token, or a person event for a sensor token. |
| 429 | {error} | Rate limit exceeded; see below. |
Retries and rate limits#
- Idempotency. Send an
Idempotency-Keyheader onPOST /hunt/{token}and a repeated request with the same key and token within 60 seconds is accepted without storing it again, answering{"accepted": true, "timestamp": …}. A key is remembered only after a successful request, so a failed one can be retried. Not available on/raw, MQTT or e-mail. - Rate limit. 300 requests per second per organisation, shared by all its tokens (a one-second sliding window); beyond that, 429. Requests with an unknown token are limited to 10 per second per client address. The limit covers the HTTP API only.
GET /hunt/meta/event-codes/{token}#
Returns the event codes a sensor accepts in eventCode. It works for sensor tokens only and is not rate limited.
{
"sensorType": "DoorContact",
"validEventCodes": ["BurglaryAlarm", "BurglaryVerified", "TamperAlarm", "MotionDetected",
"VibrationDetected", "GlassBreak", "InfraredTrip", "FenceDisturbance"]
}POST /hunt/{token}/raw#
Stores the body exactly as received as an unknown event, with no parsing or classification, for a sensor or person token. It is for debugging a new integration or for sources that manage their own payload format.
MQTT#
Huntress contains an MQTT broker on port 1883 (HuntressMqtt:Enabled, HuntressMqtt:Port). It is built for devices to report to Huntress and to receive commands from it; it is not a general-purpose broker.
- Connecting. The MQTT user name is the sensor or person token. The password is not checked. Client id
huntressis reserved. A connection with an unknown token is refused. - Publishing. Any topic is accepted; the topic is kept only as a note of where the message came from. The payload is read exactly like an HTTP body: JSON, a bare number, form fields or a raw JPEG. With MQTT 5 the content type is taken from the message; with 3.1.1 it is detected.
eventCodeandunitare honoured. - Subscribing. A connection may subscribe only to the command topics of its own output and of the other outputs of the same device (see Devices). Wildcards and
$topics are refused, and nothing a client publishes is forwarded to another client. - Limits. 2,000 connections, 10 MB per message (larger closes the connection), a 30-second communication timeout and 100 queued messages per client.
Publishing a reading
mosquitto_pub -h <host> -p 1883 -u <token> -P x -t door -m '{"value": 1}'HuntressMqtt:Enabled=false if you do not use it.SMTP#
Huntress contains an SMTP receiver on port 25 (HuntressSmtp:Enabled, HuntressSmtp:Port). It is for cameras, recorders and appliances that can report only by e-mail.
- Addressing. The local part of the first recipient is the token:
<token>@anything. The domain is ignored. A recipient that is not a token is accepted and dropped without a bounce. - Body. Plain text (a bare number is a reading), JSON, or a picture. For multipart mail the first
image/*attachment is taken and the rest are ignored. - Limits. Messages up to 10 MB (
HuntressSmtp:MaxMessageBytes); larger ones are rejected. There is no sender authentication and no STARTTLS.eventCodeandunitare not read from mail. - Retries. If Huntress cannot process a message it answers a temporary failure, so the sending server retries.
On Linux, binding port 25 needs the CAP_NET_BIND_SERVICE capability, which the supplied service unit grants. A listener that cannot start is reported on /health and does not stop the server.
Securing the plain doors#
Only the HTTP door gets TLS from your reverse proxy. MQTT, SMTP, SIA DC-09 and Contact ID are plain TCP; there is no TLS code in those listeners.
- Turn off what you do not use:
HuntressMqtt:Enabled,HuntressSmtp:Enabled,HuntressTcp:Enabled. - Expose the others only to the networks that need them: firewall rules or a VPN. A device's token or account code is its only credential.
- Prefer HTTPS for anything that travels over the internet. Devices that cannot do TLS can post to a gateway or a Huntress Edge on their own network.