Docs / Contact / sdks/python/api-reference
Python SDK: API Reference
Complete method reference for the tendrl.Client class.
Lifecycle
Client(**kwargs)
Create a new client instance. See Configuration for all parameters.
from tendrl import Client
client = Client(api_key="your_key")
client.start()
Start the client. In managed mode, this starts background threads for batching, heartbeats, and message polling. Validates the API key on first call (API mode).
client.stop()
Stop the client gracefully. Flushes remaining messages in the queue, stops background threads, and updates the entity status to offline.
Publishing
client.publish(data, tags=None, entity=None, wait_response=False)
Publish a message to Contact.
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
any | — | JSON-serializable data (dict, string, list, number) |
tags |
list[str] | None |
Tags for routing to flows (max 10) |
entity |
str | None |
Target entity identifier. Empty sends to self. |
wait_response |
bool | False |
True = send immediately and return response. False = queue for batching. |
Returns: Server response dict when wait_response=True, otherwise None.
# Queued (non-blocking)
client.publish({"temp": 23.5}, tags=["sensor"])
# Immediate with response
resp = client.publish({"alert": "high"}, wait_response=True)
@client.tether(tags=None, write_offline=False, db_ttl=3600)
Decorator that publishes the return value of a function.
| Parameter | Type | Default | Description |
|---|---|---|---|
tags |
list[str] | None |
Tags applied to the published message |
write_offline |
bool | False |
Also store locally for offline resilience |
db_ttl |
int | 3600 |
Offline storage TTL in seconds |
@client.tether(tags=["metrics"], write_offline=True)
def collect():
return {"cpu": 42.5}
collect() # Publishes {"cpu": 42.5} with tags ["metrics"]
Message Receiving
Poll for incoming messages automatically in managed mode. Route by tag or msg_type with @client.on():
client = Client(api_key="your_key")
@client.on(tag="ai-response")
def handle_ai_reply(message):
print(message.get("data"))
@client.on(tags=["alert", "anomaly"])
def handle_alert(message):
print(message.get("data"))
client.start()
@client.on(msg_type=None, tag=None, tags=None, tags_all=None)
Register a handler for incoming messages. All specified criteria must match (AND semantics). Routes are checked in registration order; first match wins.
| Parameter | Type | Description |
|---|---|---|
msg_type |
str | Match this message type |
tag |
str | Match a single tag |
tags |
list[str] | Match if message has any listed tag |
tags_all |
list[str] | Match if message has all listed tags |
@client.on_default
Catch-all handler when no route matches.
Fallback callback=
Constructor argument. Runs when no @client.on() route or @client.on_default handler matches.
Constructor arguments that control receiving:
| Argument | Default | Description |
|---|---|---|
callback |
None |
Catch-all handler for unmatched messages |
check_msg_rate |
3.0 |
Seconds between polls |
check_msg_limit |
1 |
Max messages retrieved per poll |
The handler receives a dict shaped like:
{
"msg_type": "publish",
"source": "account:region:entity:name",
"timestamp": "2025-01-15T10:30:00Z",
"data": { ... },
"tags": ["tag1"]
}
State Receiving
Poll the state table at the same interval as messages (check_msg_rate). Handlers fire when the table changes:
@client.on_state()
def handle_state(state):
if state.get("status") == "needs_maintenance":
print("Maintenance required")
@client.on_state()
Register a handler for remote state table changes detected by polling.
client.check_state()
Manually poll the state table and dispatch handlers if it changed.
Fallback state_callback=
Constructor argument. Runs when no @client.on_state() handler is registered.
File Transfer
Send and receive files between entities. Files are malware-scanned by Surface before delivery and deleted once downloaded. See File Transfer for the full model.
client.send_file(path=None, *, data=None, filename=None, dest="", tags=None)
Upload a file to an entity (dest) or a tag-routed automation (tags). Returns the response dict (transfer_id, status, …) or None on failure (e.g. 402 credits, 415 type, 422 blocked).
res = client.send_file("reading.csv", dest="gateway-01")
print(res["transfer_id"], res["status"]) # … clean
client.check_files(limit=50)
List clean files addressed to this entity (the receiver inbox).
client.download_file(transfer_id)
Download a clean file's bytes. For the default delete-on-download files, a successful download consumes the file.
for f in client.check_files():
data = client.download_file(f["transfer_id"])
State Table
The Python SDK doesn't currently wrap the state table. Call the REST endpoints directly:
| Method | Path | Purpose |
|---|---|---|
GET |
/api/entities/status-table |
Read current state |
PATCH |
/api/entities/status-table |
Merge into existing state |
PUT |
/api/entities/status-table |
Replace state entirely |
Authenticate with the entity's API key. See REST protocol → State Table for full examples.
Message Structure
Messages sent to Contact follow this structure:
{
"msg_type": "publish",
"data": { "temperature": 23.5 },
"timestamp": "2025-01-15T10:30:00Z",
"context": {
"tags": ["sensor"],
"wait": false
}
}
String data is automatically wrapped: "hello" becomes {"data": "hello"}.
Tendrl