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.

python

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.

python

# 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
python

@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():

python

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:

python

{
    "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:

python

@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).

python

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.

python

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:

json

{
  "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"}.