Docs / Contact / sdks/go/api-reference
Go SDK: API Reference
Complete method reference for the tendrl.Client type.
Constructors#
NewClient(managed bool, apiKey ...string) (*Client, error)#
Create a new client. Pass true for managed mode (recommended) or false for headless.
NewClientWithMode(managed bool) (*Client, error)#
Create a client using the TENDRL_KEY environment variable.
NewClientWithConfig(configPath string) (*Client, error)#
Create a client from a JSON config file.
NewClientWithConfigAndAPIKey(configPath, apiKey string) (*Client, error)#
Create a client from a config file with an explicit API key override.
All four constructors validate the API key before returning in managed mode, by calling /claims. An invalid or unreachable key is a constructor error, not a surprise later.
Lifecycle#
client.Stop()#
Gracefully stop the client. Flushes the message queue, stops background goroutines, and updates entity status to offline.
Publishing#
client.Publish(data interface{}, tags []string, entity string, waitResponse bool, timeout int) (string, error)#
Publish a message. When waitResponse is true, blocks until the server responds or the timeout (seconds) expires. Returns the message ID.
client.PublishAsync(data interface{}, tags []string) error#
Queue a message for batched delivery. Non-blocking. Returns an error if the queue is full.
client.PublishCrossAccount(data interface{}, destination string, tags []string) error#
Send a message to an entity in another account. The destination format is account:region:entity:name.
client.Tether(name string, fn func() (interface{}, error), tags []string, interval time.Duration) func()#
Start a periodic data collection function. Calls fn every interval and publishes the result. Returns a stop function.
Message Receiving#
Poll for incoming messages in managed mode. Route by tag or msg_type with client.On():
client.On(tendrl.MessageRoute{
Tag: "ai-response",
Handler: func(msg tendrl.IncomingMessage) error {
fmt.Println(msg.Data)
return nil
},
})
client.On(route MessageRoute)#
Register a route handler. All non-empty fields in the route must match (AND semantics). Routes are checked in registration order; first match wins.
| Field | Type | Description |
|---|---|---|
MsgType |
string | Match this message type |
Tag |
string | Match a single tag |
Tags |
[]string | Match if message has any listed tag |
TagsAll |
[]string | Match if message has all listed tags |
Handler |
MessageCallback |
Handler function |
client.OnDefault(fn func(IncomingMessage) error)#
Catch-all handler when no route matches.
client.SetMessageCallback(fn func(IncomingMessage) error)#
Catch-all fallback when no route or OnDefault handler matches. The callback receives:
type IncomingMessage struct {
MsgType string `json:"msg_type"`
Source string `json:"source"`
Dest string `json:"dest,omitempty"`
Timestamp string `json:"timestamp"`
Data interface{} `json:"data"`
Context IncomingMessageContext `json:"context,omitempty"`
RequestID string `json:"request_id,omitempty"`
}
type IncomingMessageContext struct {
Tags []string `json:"tags,omitempty"`
DynamicActions map[string]interface{} `json:"dynamicActions,omitempty"`
}
Tags are on msg.Context.Tags, not on the message itself — there is no msg.Tags field. client.On route matching reads Context.Tags for you.
client.SetMessageCheckRate(d time.Duration)#
Set polling interval for incoming messages.
client.SetMessageCheckLimit(n int)#
Set maximum messages retrieved per poll.
client.CheckMessages() error#
Poll once for incoming messages and dispatch them to your registered handlers. It returns only an error — messages reach you through On, OnDefault or SetMessageCallback, never as a return value. A managed client already polls on its own every SetMessageCheckRate interval; call this to poll on your own schedule.
State Receiving#
Poll the state table at the same interval as messages. Handlers fire when the table changes:
client.OnState(func(state map[string]interface{}) error {
if status, _ := state["status"].(string); status == "needs_maintenance" {
log.Println("Maintenance required")
}
return nil
})
client.OnState(fn func(map[string]interface{}) error)#
Register a handler for remote state table changes detected by polling.
client.SetStateCallback(fn)#
Catch-all fallback when OnState is not used.
client.CheckState() error#
Manually poll the state table and dispatch handlers if it changed.
File Transfer#
Send and receive files between entities. Files are malware-scanned by Surface before delivery and deleted once downloaded. See File Transfer.
client.SendFile(path, dest string, tags []string, meta ...map[string]any) (*FileResult, error)#
Upload a file from disk, routed by dest (entity, fanout, or cross-account resource path) or tags (Strand automation). SendFileBytes(name, data, dest, tags) uploads raw bytes. A non-2xx response (402 credits, 403 not accepted, 415 type, 422 blocked) is returned as an error.
res, _ := client.SendFile("reading.csv", "gateway-01", nil)
client.CheckFiles(limit int) ([]map[string]any, error)#
List clean files addressed to this entity.
client.DownloadFile(transferID string) ([]byte, error)#
Download a clean file's bytes (consumes delete-on-download files).
client.RescanFile(transferID string) (*RescanResult, error)#
Re-scan a received cross-account file with this account's own Surface profile (billed to the recipient). Only the recipient may call it; a blocked result means the stricter profile flagged it.
State Table#
The Go SDK reads the state table but does not wrap writes. Use client.OnState or client.CheckState to read it, and call the REST endpoints directly to write:
| 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.
Heartbeat#
client.PublishHeartbeat(data HeartbeatData) error#
Send a manual heartbeat with system metrics.
type HeartbeatData struct {
MemFree uint64
MemTotal uint64
DiskFree uint64
DiskSize uint64
}
client.GetSystemMetrics() SystemMetrics#
Get current system resource metrics, returned by value. In headless mode it returns a zero SystemMetrics, since metrics are only collected by the managed background loop.
client.PublishSensorData(sensorData interface{}, tags []string) error#
Convenience wrapper around PublishAsync for sensor readings.
client.GetOfflineStorageStats() OfflineStorageStats#
Return counts and byte totals for messages stored offline while disconnected.
Connectivity#
client.IsOnline() bool#
Check if the client has network connectivity.
client.GetConnectivityState() ConnectivityState#
Get detailed connectivity information including last check time and last online/offline transitions.
Configuration#
tendrl.GenerateExampleConfig() *ConfigFile#
Build a ConfigFile populated with every option at its documented default. It takes no arguments and writes nothing — pair it with SaveConfigFile to put it on disk:
if err := tendrl.SaveConfigFile(tendrl.GenerateExampleConfig(), tendrl.GetDefaultConfigPath()); err != nil {
log.Fatal(err)
}
tendrl.LoadConfigFile() (*ConfigFile, error)#
Load configuration from disk. It takes no path: it searches ~/.tendrl/config.json first, then /etc/tendrl/config.json on non-Windows systems, and returns the first one it can read. If neither exists it returns an empty ConfigFile and a nil error — which is why offline storage, retry, connectivity monitoring and heartbeats are off by default. See Configuration.
To load from a specific path instead, construct the client with NewClientWithConfig(path).
tendrl.SaveConfigFile(config *ConfigFile, path string) error#
Write a ConfigFile to path, creating the directory if needed.
tendrl.GetDefaultConfigPath() string#
Return ~/.tendrl/config.json, or ./tendrl.json if the home directory cannot be determined.
Tendrl