Docs / Contact / getting-started/sending-messages
Sending Messages
Send messages from your entity to Contact over HTTP. For MQTT, see MQTT Protocol. For SDK examples, see SDKs. To send a file instead of a JSON message, see File Transfer.
Endpoint
POST https://app.tendrl.com/api/entities/message
Authenticate with the entity's API key in a Bearer header.
Message body
| Field | Required | Description |
|---|---|---|
msg_type |
Yes | One of publish, heartbeat, state_new, state_update |
data |
Yes | Your payload (arbitrary key-value pairs) |
context.tags |
No | Routing tags, required if you want to trigger connectors or Strand workflows |
dest |
No | Send to another entity by name |
timestamp |
No | Defaults to server time |
curl -X POST https://app.tendrl.com/api/entities/message \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_ENTITY_API_KEY" \
-d '{
"msg_type": "publish",
"data": {"temperature": 23.5, "humidity": 65},
"context": {"tags": ["sensor"]}
}'
Success returns 200 OK with {"code": 200, "content": "<message id>"}.
If the request failed
| Status | reason |
What it means |
|---|---|---|
| 400 | Message type is required |
msg_type is missing. |
| 400 | Invalid message format |
Malformed JSON, or data isn't an object. |
| 400 | Message size exceeds the 5KB per message limit. |
Per-message cap, on every plan. Trim the payload or send a file. |
| 401 | Access Denied |
Key wrong or revoked, or Bearer missing. |
| 401 | Only entities can write messages |
You used an account key. Messages need the entity's own key. |
| 401 | Unauthorized: Missing required permission 'entity:WriteMessages' |
The entity's role can't publish. |
| 403 | Monthly data limit exceeded |
Plan data cap reached for this billing period. |
Validation is passive. A rule violation returns 200 and stores the message marked validation_status: "failed" — it does not return 400. If you expected a rejection and got a success, that's why. See Validation below.
Batch sending
Send multiple messages in one request via POST /api/entities/messages (plural). Body is a JSON array of messages.
Receiving Messages
Messages addressed to your entity (via dest, or fanned out from an fanout) are queued until the entity retrieves them. An HTTP-only device receives them by polling:
GET https://app.tendrl.com/api/entities/check_messages?limit=10
Authenticate with the receiving entity's API key. The response is an object with a messages array of pending messages:
{
"messages": [
{
"msg_type": "command",
"data": {"action": "reboot"},
"tags": ["fleet-a"],
"source": "100:us-east:entity:controller-1",
"timestamp": "2026-06-15T10:30:00Z"
}
]
}
| Param | Default | Description |
|---|---|---|
limit |
1 |
Maximum number of pending messages to return |
Each returned message is atomically marked as delivered, so it is never handed out twice, even if multiple clients poll concurrently. Poll on an interval (or use MQTT for real-time push) to drain the queue. If there are no pending messages, messages is an empty array.
Validation
If the entity is assigned a service, publish messages run through that service's validation rules. Results are attached as actionResults in the stored message and forwarded to the destination entity when the service has Forward Validation enabled. Validation is passive: a message that fails is still stored (marked validation_status: "failed"), not rejected. See Validation Rules.
Next steps
- Messages overview: message structure and routing
- REST protocol: full endpoint reference
- SDKs: Python, Go, JavaScript, MicroPython clients
Tendrl