Docs / Contact / protocols/rest
REST Protocol Guide
Send and receive messages over HTTPS using Contact's REST API. This is useful for server-side applications, request-response workflows, or any environment where HTTP is preferred over MQTT.
Base URL
https://app.tendrl.com/api
Authentication
All message endpoints require an entity API key passed as a Bearer token:
Authorization: Bearer YOUR_ENTITY_API_KEY
Getting Your API Key
Contact issues an API key automatically when you create the entity. To retrieve it:
- Open the entity in the Contact dashboard
- Click Connection Instructions
- Copy the API Key value (the secret)
The secret is shown once at creation. If you lose it, rotate the key from Access Control → API Keys.
---
Sending Messages
Single Message
POST /api/entities/message
Send one message at a time. Use this for heartbeats or when you need the server response immediately.
curl -X POST https://app.tendrl.com/api/entities/message \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"msg_type": "publish",
"data": {
"temperature": 23.5,
"humidity": 65
},
"context": {
"tags": ["sensor", "building-a"]
}
}'
Response (200 OK):
{
"code": 200,
"content": "<message id>"
}
Batch Messages
POST /api/entities/messages
Send multiple messages in a single request. More efficient for high-throughput scenarios.
curl -X POST https://app.tendrl.com/api/entities/messages \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY" \
-H "Content-Type: application/json" \
-d '[
{
"msg_type": "publish",
"data": {"temperature": 23.5},
"context": {"tags": ["sensor"]}
},
{
"msg_type": "publish",
"data": {"temperature": 24.1},
"context": {"tags": ["sensor"]}
}
]'
---
Message Types
msg_type |
Purpose | Batching |
|---|---|---|
publish |
Standard data message: telemetry, events, commands | Yes |
heartbeat |
Health check with system metrics. Updates entity online status. | Yes |
state_new |
Replace the entity's state table with data |
Yes |
state_update |
Merge data into the entity's existing state table |
Yes |
Publish Message
The default message type. Validated against service rules (if assigned), stored in message history, and can trigger flows.
{
"msg_type": "publish",
"data": {
"temperature": 23.5,
"humidity": 65,
"location": "Building A"
}
}
Send to a Specific Entity
Add the dest field to route the message to another entity by name:
{
"msg_type": "publish",
"data": {
"alert": "Temperature threshold exceeded",
"value": 98.6
},
"dest": "control-panel-01"
}
Heartbeat
Heartbeats update the entity's online status and are stored for monitoring. They skip validation rules. Required fields: mem_free, mem_total, disk_free, disk_size.
{
"msg_type": "heartbeat",
"data": {
"mem_free": 1024.0,
"mem_total": 4096.0,
"disk_free": 50000.0,
"disk_size": 100000.0
}
}
Response (200 OK): Empty body.
State New
Replaces the entity's entire state table with the contents of data:
{
"msg_type": "state_new",
"data": {
"firmware_version": "1.2.0",
"mode": "active",
"last_calibrated": "2024-01-10"
}
}
State Update
Merges data into the entity's existing state table. Only the specified keys are updated:
{
"msg_type": "state_update",
"data": {
"mode": "standby"
}
}
---
State Table
The state table is a persistent key-value store on each entity. You can update it via messages (state_new, state_update) or manage it directly with these endpoints.
Read State Table
GET /api/entities/status-table
Returns the authenticated entity's current state table.
curl https://app.tendrl.com/api/entities/status-table \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY"
Response (200 OK):
{
"statusTable": {
"firmware_version": "1.2.0",
"mode": "active",
"last_calibrated": "2024-01-10"
}
}
Update State Table (Merge)
PATCH /api/entities/status-table
Merges the provided fields into the existing state table. Keys not included are left unchanged.
curl -X PATCH https://app.tendrl.com/api/entities/status-table \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode": "standby"}'
Replace State Table
PUT /api/entities/status-table
Replaces the entire state table with the provided object.
curl -X PUT https://app.tendrl.com/api/entities/status-table \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"firmware_version": "2.0.0", "mode": "active"}'
The state_new and state_update message types go through the message pipeline (validation, flow triggers). The direct PATCH/PUT endpoints update the state table immediately without message processing.
---
Receiving Messages
Check for Pending Messages
GET /api/entities/check_messages?limit=5
Poll for messages sent to your entity. Messages are atomically marked as processed when retrieved, so each message is delivered only once.
curl https://app.tendrl.com/api/entities/check_messages?limit=5 \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY"
Response (200 OK):
{
"messages": [
{
"msg_type": "publish",
"data": {
"command": "reboot",
"reason": "firmware update"
},
"tags": ["maintenance"],
"source": "123456:us-1:entity:control-panel",
"timestamp": "2024-01-15T10:30:45.123456Z"
}
]
}
If no messages are pending, messages will be an empty array.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer | 1 | Maximum number of messages to retrieve |
List Message History
GET /api/entities/messages
Paginated list of all non-heartbeat messages in your account.
curl "https://app.tendrl.com/api/entities/messages?limit=20" \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY"
This endpoint uses the same entity API key as every other endpoint, but requires the entity:ListMessages permission. The default entity role does not include it, so an admin must grant it explicitly before an entity can list or fetch message history.
| Parameter | Type | Description |
|---|---|---|
entity_name |
string | Filter by entity name |
start_date |
string | Start date (RFC 3339) |
end_date |
string | End date (RFC 3339) |
limit |
integer | Results per page (default: 10) |
next_cursor |
string | Cursor for next page |
Get a Single Message
GET /api/entities/messages/:id
curl https://app.tendrl.com/api/entities/messages/MESSAGE_ID \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY"
---
File Transfer
Entities can also send files (not just JSON messages). Files are malware-scanned by Surface before delivery and deleted once downloaded:
POST /api/entities/files # upload (multipart: file, dest=recipient entity)
GET /api/entities/files # receiver inbox (clean files for you)
GET /api/entities/files/download/:id # download (consumes the file)
GET /api/files?kind=clip # account clip gallery
GET /api/files/:id/video # MP4 preview (jpeg_burst clips)
GET /api/files/:id/poster # poster thumbnail
GET /api/files/:id/raw # original zip or legacy GIF
See File Transfer for the full reference, limits, and error codes.
---
Message Payload Reference
Outbound (Entity to Contact)
| Field | Type | Required | Description |
|---|---|---|---|
msg_type |
string | Yes | publish, heartbeat, state_new, or state_update |
data |
object | Yes* | Your message payload (arbitrary key-value pairs). *Required fields for heartbeat. |
timestamp |
string | No | RFC 3339 timestamp. Defaults to server time if omitted. |
dest |
string | No | Destination entity name for directed messages |
context |
object | No | Additional context (see below) |
Context Object
| Field | Type | Description |
|---|---|---|
tags |
string[] | Tags for routing, filtering, and fanout fan-out (max 10) |
Inbound (Contact to Entity via check_messages)
| Field | Type | Description |
|---|---|---|
msg_type |
string | Message type |
data |
object | Message payload |
tags |
string[] | Tags from the original message |
source |
string | Sender entity resource path |
timestamp |
string | Original message timestamp |
---
Validation
Messages from entities with a service assigned are validated automatically. The response to check_messages includes validation results in the data field when validation fails.
| Status | Meaning |
|---|---|
passed |
All validation rules passed |
failed |
One or more rules failed; actionResults included in data |
skipped |
No validation applied (heartbeats, state messages, or no service) |
---
Quick Start Examples
Python (requests)
API_KEY = "your-entity-api-key"
BASE_URL = "https://app.tendrl.com/api"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
# Send a message
resp = requests.post(f"{BASE_URL}/entities/message", headers=HEADERS, json={
"msg_type": "publish",
"data": {"temperature": 23.5, "humidity": 65},
"context": {"tags": ["sensor"]}
})
print(resp.status_code, resp.json())
# Check for incoming messages
resp = requests.get(f"{BASE_URL}/entities/check_messages?limit=5", headers=HEADERS)
messages = resp.json().get("messages", [])
for msg in messages:
print(f"From {msg['source']}: {msg['data']}")
JavaScript (fetch)
const API_KEY = "your-entity-api-key";
const BASE_URL = "https://app.tendrl.com/api";
const headers = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
// Send a message
await fetch(`${BASE_URL}/entities/message`, {
method: "POST",
headers,
body: JSON.stringify({
msg_type: "publish",
data: { temperature: 23.5, humidity: 65 },
context: { tags: ["sensor"] },
}),
});
// Check for incoming messages
const resp = await fetch(`${BASE_URL}/entities/check_messages?limit=5`, { headers });
const { messages } = await resp.json();
messages.forEach((msg) => console.log(`From ${msg.source}:`, msg.data));
curl
# Send a message
curl -X POST https://app.tendrl.com/api/entities/message \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"msg_type":"publish","data":{"temperature":23.5},"context":{"tags":["sensor"]}}'
# Send a heartbeat
curl -X POST https://app.tendrl.com/api/entities/message \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"msg_type":"heartbeat","data":{"mem_free":1024,"mem_total":4096,"disk_free":50000,"disk_size":100000}}'
# Check for messages
curl https://app.tendrl.com/api/entities/check_messages?limit=5 \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY"
# Send a batch
curl -X POST https://app.tendrl.com/api/entities/messages \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY" \
-H "Content-Type: application/json" \
-d '[{"msg_type":"publish","data":{"temp":22.5}},{"msg_type":"publish","data":{"temp":23.1}}]'
---
Error Responses
| Status Code | Meaning |
|---|---|
| 400 | Bad request: invalid message format or unsupported message type |
| 401 | Unauthorized: missing or invalid API key |
| 413 | Message data exceeds the 5 KB per-message limit (all plans) |
| 429 | Too many requests: monthly data limit exceeded. Only telemetry publishes are paused: heartbeats, alerts, and command acknowledgements always flow, so your fleet stays visible. You get an in-app warning at 80% and again when the limit is reached. |
| 500 | Internal server error |
Error response body:
{
"reason": "Message type 'unknown' is not supported. Allowed types: heartbeat, publish, state_new, state_update, client_cmd_resp."
}
Tendrl