Wire Protocol
VeltrixDB speaks two protocols over the same TCP port: a human-readable text protocol for ad-hoc use with nc or telnet, and a compact binary protocol that every official SDK uses by default. The server auto-detects which one a connection is speaking from the first byte.
Text protocol
The text protocol is line-oriented — one command per line, a plain-text response per line — which makes it usable directly from nc, telnet, or any tool that can write to a TCP socket. It's the fastest way to sanity-check a running server.
| Command | Response |
|---|---|
PUT key value | OK |
GET key | value (or ERR if not found) |
DEL key | OK |
PING | PONG |
INFO | keys=N writes=N reads=N ... |
AUTH user pass | OK |
QUIT | BYE |
Text commands added in v1.1.0
v1.1.0 brings the text protocol to parity with operations that were previously binary-only (TTL writes, atomics, batch get) and adds the new list and set data types. The usage lines below are exactly what the server prints on a malformed invocation (ERR usage: ...):
| Command | Response |
|---|---|
PUTEX <key> <ttl-seconds> <value> | OK — a PUT with expiration (the text-protocol counterpart of the binary PUT's TTL field). TTL must be ≥ 1; ERR bad ttl otherwise. |
INCR <key> [delta] / DECR <key> [delta] | The new integer value, as a decimal line. delta defaults to 1; a missing key counts from zero. |
SETNX <key> <value> | 1 (created) or 0 (key already exists). |
CAS <key> <expected> <new> | OK (swapped), MISMATCH (current value differed), or NOTFOUND (key absent). |
MGET <key> [key ...] | One <key> <value> line per found key (missing keys are silently omitted), terminated by END. |
LPUSH <key> <value> / RPUSH <key> <value> | The new list length. |
LPOP <key> / RPOP <key> | The popped value, or NIL when the list is empty. |
LLEN <key> | The element count. |
LRANGE <key> <start> <stop> | One element per line, then END. Redis semantics: stop inclusive, negative indices count from the end. |
SADD <key> <member> / SREM <key> <member> | 1 when added/removed, 0 otherwise. |
SISMEMBER <key> <member> | 1 or 0. |
SMEMBERS <key> | One member per line (unordered), then END. |
SCARD <key> | The member count. |
CAS parses <expected> as a single space-delimited token — use the binary protocol for values containing spaces. Semantics of every command (per-key TTL, atomic RMW guarantees, list/set encoding) are covered in the Data Model, which also documents the VSET/VSEARCH vector commands.Worked example
Start a server and drive it with nc:
$ docker run -p 9000:9000 ghcr.io/veltrixdb/veltrixdb:latest
$ nc localhost 9000
PUT hello world
OK
GET hello
world
PUT counter 41
OK
GET counter
41
DEL hello
OK
GET hello
ERR
PING
PONG
INFO
keys=1 writes=3 reads=3 ...
QUIT
BYE
Or pipe a whole session in one shot, as shown in the project's own quick-start:
echo -e "PUT hello world\nGET hello\nPING" | nc localhost 9000
Binary protocol
The binary protocol is what every official SDK (Go, Python, Node.js, Java, Rust, C++) speaks by default, and it's what the server assumes once it sees a leading byte that matches a known opcode rather than ASCII command text — no handshake or negotiation is required, the server simply auto-detects the framing per connection.
Frame layout
Request: [1B cmd][2B keyLen][4B valLen][key][value]
Response: [1B status][4B payloadLen][payload]
Opcodes
| Category | Opcode | Command |
|---|---|---|
| Single-key | 0x01 | PUT |
0x02 | GET | |
0x03 | DEL | |
0x04 | PING | |
0x05 | INFO | |
| Batch | 0x06 | MPUT (vectorized multi-put) |
0x07 | MGET (vectorized multi-get) | |
| Atomic | 0x18 | CAS (compare-and-swap) |
0x19 | INCR | |
0x1A | DECR | |
0x1B | SETNX |
The atomic opcodes 0x18–0x1B use extended frames that carry the extra operands (expected value, delta, TTL) — as of v1.1.0 the same four operations are also available as text commands (CAS/INCR/DECR/SETNX, above) and in every official SDK:
CAS: [1B 0x18][2B keyLen][4B expectedLen] + [4B newLen][4B ttl] + key + expected + new
INCR: [1B 0x19][2B keyLen][4B 0] + [8B delta int64] + [4B ttl] + key
DECR: [1B 0x1A][2B keyLen][4B 0] + [8B delta int64] + [4B ttl] + key
SETNX: [1B 0x1B][2B keyLen][4B valLen] + [4B ttl] + key + value
Status codes
| Code | Status | Meaning |
|---|---|---|
0x00 | OK | Request succeeded; payload holds the result, if any. |
0x01 | ERR | Request failed; payload holds an error message. |
0x02 | NOT_FOUND | Key does not exist or is expired/tombstoned. |
0x03 | EXISTS | SETNX: the key already existed; nothing was written. |
0x04 | MISMATCH | CAS: the expected value did not match the current value; nothing was written. |
0x05 | CONFLICT | TXN: an optimistic version check failed — re-read and retry. |
MPUT/MGET batches are roughly 50× faster than issuing the same number of individual PUT/GET calls, because a batch amortizes one round trip and one group-commit window across many keys instead of paying that cost per key. All official SDKs expose batch methods (for example, db.mput(...) / db.mget(...)) — reach for them whenever you're writing or reading more than a handful of keys at once.Protocol auto-detection
Both protocols share the same TCP listener (-addr, default :9000). A single connection is either all-text or all-binary — the server determines this from the leading byte of the first request and does not switch mid-connection. You never need to configure which protocol to use; connecting with nc/telnet naturally speaks text, and every official SDK's connection pool speaks binary.