Skip to main content

TagoTiP over TCP

Tutorial

Follow our step-by-step tutorial to connect your first device over TCP: TagoTiP TCP: Quick Start

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
Location@=speed:=10@=39.74,-104.99
Timestamp@temperature:=25.5@1694567890000
Group^temperature:=25.5^batch_01
Metadata{}temperature:=25.5{source=dht22}

All combined: temperature:=25.5#C@=39.74,-104.99@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.