Skip to main content

TagoTiP over TCP

Guaranteed delivery and real-time commands. TagoTiP over TCP ensures every data point arrives in order - and the server can push commands to your device the moment they are ready.

Endpoint

RegionHostIPPorts
US-East-1tcp.tip.us-e1.tago.io75.2.126.1705693 (plaintext) / 5694 (TLS)
EU-West-1tcp.tip.eu-w1.tago.io15.197.224.1535693 (plaintext) / 5694 (TLS)

Both ports accept TagoTiP and TagoTiP(s). The server detects the mode once per connection by inspecting the first byte.

Why TCP?

  • Reliable delivery - every frame is acknowledged
  • Persistent connections - one connect, many frames
  • Real-time commands - server pushes CMD frames instantly
  • Ordered data - frames arrive in sequence

How it works

Device                                        TagoIO
| |
|── PUSH|hash|serial|[temp:=25]\n ──────────> |
|<──────── ACK|OK|1\n ─────────────────────── |
| |
|<──────── ACK|CMD|reboot\n ───────────────── | (server push)

The connection stays open. The server can push CMD frames at any time - no polling needed. Each frame must end with \n (byte 0x0A).

Arduino example (ESP32)

#include <WiFi.h>
#include <WiFiClient.h>

const char* SSID = "your-wifi";
const char* PASSWORD = "your-password";
const char* TIP_HOST = "tcp.tip.us-e1.tago.io";
const int TIP_PORT = 5693;
const char* TOKEN_HASH = "4deedd7bab8817ec"; // replace with yours
const char* SERIAL_N = "sensor-01"; // replace with yours

WiFiClient client;

void setup() {
Serial.begin(115200);
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("WiFi connected");

if (!client.connect(TIP_HOST, TIP_PORT)) {
Serial.println("Connection failed");
return;
}
Serial.println("Connected to TagoTiP TCP");
}

void loop() {
// reconnect if needed
if (!client.connected()) {
client.connect(TIP_HOST, TIP_PORT);
return;
}

float temperature = analogRead(34) * 0.1; // example reading

char frame[256];
snprintf(frame, sizeof(frame),
"PUSH|%s|%s|[temperature:=%.1f#C]\n",
TOKEN_HASH, SERIAL_N, temperature);

client.print(frame);

// read response
unsigned long timeout = millis() + 5000;
while (!client.available() && millis() < timeout) delay(10);

if (client.available()) {
String response = client.readStringUntil('\n');
Serial.println(response); // ACK|OK|1

// check for server-pushed commands
if (response.startsWith("ACK|CMD|")) {
String cmd = response.substring(8);
Serial.print("Command received: ");
Serial.println(cmd);
}
}

delay(10000); // send every 10 seconds
}

Quick test with netcat

Replace 4deedd7bab8817ec with your token hash and sensor-01 with your serial.

Push a temperature reading

echo 'PUSH|4deedd7bab8817ec|sensor-01|[temperature:=25.5#C]' \
| nc tcp.tip.us-e1.tago.io 5693
ACK|OK|1

Interactive session

Open a persistent connection:

nc tcp.tip.us-e1.tago.io 5693

Type frames and see responses in real time:

PING|4deedd7bab8817ec|sensor-01
ACK|PONG
PUSH|4deedd7bab8817ec|sensor-01|[temperature:=25.5#C;humidity:=60#%]
ACK|OK|2
PULL|4deedd7bab8817ec|sensor-01|[temperature]
ACK|OK|[temperature:=25.5#C@1694567890000]

Push a batch with timestamps (datalogger)

echo 'PUSH|4deedd7bab8817ec|logger-01|[temp:=25.5@1694567890000;temp:=26.1@1694567900000;temp:=25.8@1694567910000]' \
| nc tcp.tip.us-e1.tago.io 5693
ACK|OK|3

Push raw payload (passthrough)

echo 'PUSH|4deedd7bab8817ec|sensor-01|>xDEADBEEF01020304' \
| nc tcp.tip.us-e1.tago.io 5693

Raw bytes are delivered to your device's Payload Parser.

Pull the last stored values

echo 'PULL|4deedd7bab8817ec|sensor-01|[temperature;humidity]' \
| nc tcp.tip.us-e1.tago.io 5693
ACK|OK|[temperature:=25.5#C@1694567890000;humidity:=60#%@1694567890000]

Receiving commands

On a persistent connection, the server pushes commands instantly:

ACK|CMD|reboot
ACK|CMD|ota=https://example.com/v2.1.bin

No polling needed - just keep the connection open.

Using TLS (port 5694)

echo 'PUSH|4deedd7bab8817ec|sensor-01|[temperature:=25.5#C]' \
| openssl s_client -connect tcp.tip.us-e1.tago.io:5694 -quiet

Same protocol, same frames - TLS is handled by the load balancer.

Operators

OperatorTypeExample
:=Numbertemperature:=25.5
=Stringstatus=online
?=Booleanactive?=true
@=Location (lat,lng or lat,lng,alt)position@=39.74,-104.99

Suffixes

Append after the value, in this order:

SuffixPrefixExample
Unit#temperature:=25.5#C
Timestamp@temperature:=25.5@1694567890000
Group^temperature:=25.5^batch_01
Metadata{}temperature:=25.5{source=dht22}

All combined: temperature:=25.5#C@1694567890000^batch_01{source=dht22,quality=high}

Response codes

ResponseMeaning
ACK|OK|NN data points stored
ACK|OK|[...]PULL response with variable data
ACK|PONGKeepalive acknowledged
ACK|CMD|<command>Server command (pushed at any time)
ACK|ERR|invalid_tokenInvalid or expired token hash
ACK|ERR|device_not_foundSerial not found under your account
ACK|ERR|invalid_payloadMalformed frame or body
ACK|ERR|invalid_seqCounter not greater than last accepted
ACK|ERR|rate_limitedBack off and retry
ACK|ERR|payload_too_largeFrame exceeds max payload size
ACK|ERR|server_errorRetry after a delay

Limits

Protocol limits

LimitValue
Max frame size (wire)16,384 bytes
Max variables per frame100
Max metadata pairs32
Variable name length100 chars
Unit length25 chars
Serial length100 chars

RPM = requests per minute.

Per-profile rate limits

ResourceScaleStarterFree
Uplink RPM (PUSH)1,00050060
Downlink RPM (PULL)1,00050060
Connections per IP20103

Per-device limits

ResourceScaleStarterFree
Max payload size100 KB100 KB100 KB
Connection TTL15 s10 s10 s
Keep-alive idle timeout5 s5 s5 s

PING is exempt from rate limiting on TCP.

warning

Send a PING before the keep-alive idle timeout to keep the connection alive. The connection is closed after the TTL regardless of activity.

Specification

For the complete protocol grammar, parsing rules, and ABNF, see the TagoTiP Specification.