Use ColibriDB from your code.
Every way to talk to a cluster: HTTP calls, Redis commands and the client libraries, with examples you can paste. The Connect page of your Admin console prints your cluster's exact addresses and credentials.
Connect
Applications talk to the proxies, which route every key to the node that owns it and follow failovers for you. Pick the surface that suits your stack; they all see the same data.
| Endpoint | Default port | Use it for | Authentication |
|---|---|---|---|
HTTP proxy | 5006 | curl, any language, the Python and TypeScript clients | Bearer token, Basic, JWT (OIDC) or an ACL user |
Binary proxy | 6400 | The .NET client: the lowest latency | API key or an ACL user |
RESP on the nodes | 6379 | redis-cli and any Redis client (StackExchange.Redis, redis-py, ioredis, go-redis) | AUTH with the cluster key or an ACL user |
# From Admin → Connect: the HTTP proxy address and a token
export COLIBRI_URL=https://db.example.com:5006
export COLIBRI_TOKEN=your-proxy-tokenKeys over HTTP
The value is the raw request body, any bytes up to the configured limit. The key is one path segment: write a / inside a key as %2F.
# Write a value that expires in one hour
curl -X PUT "$COLIBRI_URL/kv/user:42?ttlSeconds=3600" \
-H "Authorization: Bearer $COLIBRI_TOKEN" \
--data-binary '{"name":"Ada","plan":"pro"}'
# Read it back (404 when the key does not exist)
curl "$COLIBRI_URL/kv/user:42" -H "Authorization: Bearer $COLIBRI_TOKEN"
# Write only if the key is absent: a simple lock (409 if someone holds it)
curl -X PUT "$COLIBRI_URL/kv/lock:report?nx=true&ttlSeconds=30" \
-H "Authorization: Bearer $COLIBRI_TOKEN" --data-binary "worker-7"
# List keys by prefix, 100 at a time (values come base64; pass nextCursor to continue)
curl "$COLIBRI_URL/kv?prefix=user:&limit=100" -H "Authorization: Bearer $COLIBRI_TOKEN"
# Delete it
curl -X DELETE "$COLIBRI_URL/kv/user:42" -H "Authorization: Bearer $COLIBRI_TOKEN"| Call | What it does | Answers |
|---|---|---|
GET /kv/{key} | Reads the value | 200 with the bytes · 404 |
PUT /kv/{key} | Writes the value. ?ttlSeconds=N expires it, ?nx=true writes only if absent, ?xx=true only if present | 204 · 409 (nx) · 404 (xx) · 413 too large |
DELETE /kv/{key} | Deletes the key | 204 · 404 |
GET /kv?prefix=&limit=&cursor= | Scans keys in pages | { items:[{key, value}], nextCursor } |
GET /health | Liveness and role, no credentials needed | 200 |
Hashes, sets, sorted sets and lists
The same atomic operations as the Redis commands, with JSON bodies (Content-Type: application/json). Strings travel as text; add ?encoding=base64 to send and receive binary values.
# A hash: set fields, increment one, read them all
curl -X PUT "$COLIBRI_URL/hash/cart:7" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"fields":{"sku-1":"2","sku-9":"1"}}'
curl -X POST "$COLIBRI_URL/hash/cart:7/incr" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"field":"sku-1","by":3}'
curl "$COLIBRI_URL/hash/cart:7" -H "Authorization: Bearer $COLIBRI_TOKEN"
# → {"key":"cart:7","count":2,"fields":{"sku-1":"5","sku-9":"1"}}
# A sorted set: add scores, read the top 10
curl -X PUT "$COLIBRI_URL/zset/leaderboard" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"members":[{"member":"ada","score":120},{"member":"bob","score":95}]}'
curl "$COLIBRI_URL/zset/leaderboard?start=0&stop=9&rev=true" -H "Authorization: Bearer $COLIBRI_TOKEN"
# A list as a queue: push at the end, pop from the front
curl -X POST "$COLIBRI_URL/list/jobs/push" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"side":"right","values":["job-1","job-2"]}'
curl -X POST "$COLIBRI_URL/list/jobs/pop" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"side":"left","count":1}'
# A set: add members, test one
curl -X PUT "$COLIBRI_URL/set/tags:42" -H "Authorization: Bearer $COLIBRI_TOKEN" \
-H "Content-Type: application/json" -d '{"members":["vip","beta"]}'
curl "$COLIBRI_URL/set/tags:42?member=vip" -H "Authorization: Bearer $COLIBRI_TOKEN"| Type | Calls | Like |
|---|---|---|
/hash/{key} | GET (?field=…), PUT { fields }, DELETE ?field=…, POST /incr { field, by } | HGETALL, HSET, HDEL, HINCRBY |
/set/{key} | GET (?member=…), PUT { members }, DELETE ?member=…, POST /pop { count } | SMEMBERS, SADD, SREM, SPOP |
/zset/{key} | GET ?start&stop&rev or ?min&max, PUT { members:[{member, score}] }, POST /incr, POST /pop | ZRANGE, ZADD, ZINCRBY, ZPOPMIN |
/list/{key} | GET ?start&stop, POST /push { side, values }, POST /pop { side, count }, POST /trim | LRANGE, LPUSH/RPUSH, LPOP/RPOP, LTRIM |
Redis commands
Turn on RESP for the nodes (Admin → Configuration) and use redis-cli or the Redis client you already have. With TLS on, connect with --tls or a rediss:// URL.
redis-cli -h db.example.com -p 6379 --tls -a "$COLIBRI_PASSWORD"
SET user:42 '{"name":"Ada"}' EX 3600
GET user:42
HSET cart:7 sku-1 2 sku-9 1
ZADD leaderboard 120 ada 95 bob
ZREVRANGE leaderboard 0 9 WITHSCORES
MULTI
INCR visits:today
EXPIRE visits:today 86400
EXECCompatibility is verified by a test suite and by client checks with StackExchange.Redis, redis-py and ioredis, over RESP2 and RESP3:
| Area | Status | Notes |
|---|---|---|
| Connection, AUTH, PING, HELLO 2 and 3, pipelining | Supported | Writes in one pipeline share a group commit; see Pipelined writes below |
| Strings, TTL, SET options, INCR, APPEND, GETRANGE and SETRANGE | Supported | Expiry is lazy plus a periodic sweep |
| Keys: KEYS, SCAN, RENAME, COPY and TOUCH | Supported | Multi-key commands need keys in one shard; use a {hash tag} |
| Hashes | Supported | Whole collection rewritten per write, 4 MiB cap |
| Sets, sorted sets and lists | Partial | Unions, intersections, STORE variants, LMOVE and lex ranges included; no blocking operations yet |
| Bitmaps | Supported | SETBIT, GETBIT, BITCOUNT, BITPOS and BITOP; no BITFIELD |
| MULTI, EXEC and WATCH | Partial | Keys must live in one shard; use {hash tags}. EXEC is atomic and crash-safe |
| Pub/sub | Supported | Cluster-wide when the relay is configured, otherwise node-local |
| Keyspace notifications | Supported | Events for writes from every protocol |
| RESP3 | Supported | Maps, sets, doubles and push frames after HELLO 3 |
| Client-side caching (CLIENT TRACKING) | Not supported | |
| Streams, Lua and EVAL, HyperLogLog, geo | Not supported | |
| FLUSHDB, FLUSHALL, SELECT above 0, CLUSTER | Not supported | Refused |
Client libraries
The client packages come with your license. They follow leader changes, retry reads safely and never repeat a write that may already have been applied.
using ColibriDB.Client;
await using var db = new ColibriClient(new ColibriClientOptions
{
Host = "db.example.com",
Port = 6400,
ApiKey = Environment.GetEnvironmentVariable("COLIBRI_KEY"),
});
await db.PutAsync("user:42", "{\"name\":\"Ada\"}");
string? user = await db.GetStringAsync("user:42");
var many = await db.GetManyAsync(["user:1", "user:2", "user:3"]);import os
from colibridb import ColibriClient
with ColibriClient(os.environ["COLIBRI_URL"], bearer=os.environ["COLIBRI_TOKEN"]) as db:
db.put("user:42", '{"name":"Ada"}', ttl_seconds=3600)
db.get_text("user:42") # '{"name":"Ada"}'
db.put("lock:report", "worker-7", nx=True) # False if the key exists
db.hash_set("cart:7", {"sku-1": "2"})
db.zset_add("leaderboard", {"ada": 120, "bob": 95})
for key, value in db.iter_scan("user:"):
print(key, value)import { ColibriClient } from "@colibridb/client";
const db = new ColibriClient({
baseUrl: process.env.COLIBRI_URL!,
bearer: process.env.COLIBRI_TOKEN!,
});
await db.put("user:42", JSON.stringify({ name: "Ada" }), { ttlSeconds: 3600 });
const user = await db.getText("user:42");
await db.put("lock:report", "worker-7", { nx: true }); // false if the key exists
await db.zsetAdd("leaderboard", new Map([["ada", 120], ["bob", 95]]));
for await (const [key, value] of db.iterScan("user:")) {
console.log(key, value);
}| Client | Protocol | GET on loopback | Notes |
|---|---|---|---|
.NET | Binary, port 6400 | about 37 µs | Pooled, pipelined connections; TLS and mTLS. No TTL or scan on this protocol: use HTTP for those |
Python | HTTP, port 5006 | about 75 µs | Python 3.9+, one keep-alive connection per thread; hashes, sets, sorted sets, lists and vectors |
TypeScript | HTTP, port 5006 | about 65 µs | Node, Deno, Bun and browsers; the same typed methods as Python |
Errors and retries
Errors come as Problem Details JSON (type, title, detail). The clients handle redirects and safe retries for you; with plain HTTP, this is what each status means and what to do.
| Status | Meaning | What to do |
|---|---|---|
401 · 403 | Missing or wrong credentials, or the ACL denies this key | Check the token or the user's key patterns |
404 | The key does not exist (or xx=true found nothing) | Treat as a miss |
409 | nx=true found the key, or the key holds another type | Read the key, or use another name |
413 | Value or collection over the size limit | Split the value; nothing was written |
421 | The node is not the leader of this key's group | Repeat at X-Colibri-Leader-Url; nothing was applied |
429 | The ACL user's rate limit | Back off and retry |
503 | Election, migration or no quorum (Retry-After) | Retry reads; retry writes only with X-Colibri-Fenced: raft-not-applied |
504 | The write's outcome is unknown | Read the key before repeating the write |
507 | The tenant's quota is full | Reads and deletes still work; free space or raise the quota |
Pipelined writes and durability
A client that sends writes back to back on one connection gets them committed together, and the replies stay in request order. These are the settings and what they guarantee.
| Setting | Default | Range | Notes |
|---|---|---|---|
| Node__PipelinedWrites | 128 | 0 to 4096 | Plain SET and PUT writes in flight per connection, on RESP and the binary protocol. 0 turns it off. |
| Node__PipelinedWriteBytes | 1 MiB | 1 B to 256 MiB | Key and value bytes in flight per connection. A single larger write is not deferred. |
| ProxyBinary__PipelinedFrames | 128 | 0 to 1024 | GET, PUT and DELETE frames the binary proxy forwards to a node before reading replies. 0 or 1 turns it off. |
| ProxyBinary__PipelinedBytes | 1 MiB | 0 to 1 GiB | Request bytes in one forwarded batch. 0 turns it off. |
| ProxyBinary__PipelinedResponseBytes | 2 MiB | 64 KiB to 1 GiB | Replies a batch may hold out of order. Over it, the proxy stops reading that node connection until its turn. |
| ProxyBinary__MaxIdleBackendConnectionsPerNode | 1024 | 0 to 10000 | Idle connections kept open per node. Keep it at or above the real number of concurrent clients. |
| ProxyBinary__BackendIdleTimeoutSeconds | 240 | 1 to 299 | Idle seconds after which a connection to a node is closed. It stays below the node's 300 s idle timeout. |
- Any other command, a repeated key or a protocol error ends the run first, so a command that follows a write in the same pipeline sees it.
- With WalPolicy Always, a run costs one fsync instead of one per write. Raft groups always use Always.
- With WalPolicy Interval, the periodic fsync runs in a background task and writes do not wait for it. The loss window is still at most WalFlushIntervalMs.
- If the fsync of the file replication log fails, the shard keeps serving and replication degrades: node health reports Degraded, the metric colibri.engine.replication_faulted_shards counts the shard and a .gap marker stays beside the log. Replay refuses a log with a hole, so the replica must be resynced.
- The log API, replication and the vector index never see a log position before its data is readable. If applying a write to the index fails after the commit, the shard stops instead of answering with a misleading error.
- A failed fsync while a segment rotates is never reported as durable.
Architecture, security, operations and the full configuration reference come with your license. Ask us for them