CapyDB/ docs
GuidesK/V

Clients

Connecting to a CapyDB K/V store over the Upstash REST protocol or RESP - @upstash/redis, @capydb/kv, ioredis, redis-py, go-redis and redis-cli.

A K/V store speaks two protocols. Both are standard, so migrating in is a URL and a token.

ProtocolEndpointUse it from
Upstash REST over HTTPSCAPYKV_REST_URLServerless and edge runtimes, anything without a connection pool
RESP over TLSCAPYKV_REDIS_URLLong-lived servers, blocking commands, pub/sub

Upstash REST

The REST protocol carries pipelining, transactions and base64 encoding, so @upstash/redis works at its default settings - including Upstash-Encoding: base64 and auto-pipelining, both of which the client turns on for you.

import { Redis } from '@upstash/redis'

const redis = new Redis({
  url: process.env.CAPYKV_REST_URL!,
  token: process.env.CAPYKV_REST_TOKEN!,
})

await redis.set('user:1', { name: 'Ada' })
const user = await redis.get<{ name: string }>('user:1')

const [count, ttl] = await redis
  .pipeline()
  .incr('visits')
  .ttl('visits')
  .exec()

Redis.fromEnv() reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN. CapyDB publishes its own names, so either construct the client explicitly or set the UPSTASH_* names yourself.

Calling the endpoint directly, without a client:

curl "$CAPYKV_REST_URL/set/greeting/hello" \
  -H "Authorization: Bearer $CAPYKV_REST_TOKEN"

curl "$CAPYKV_REST_URL" \
  -H "Authorization: Bearer $CAPYKV_REST_TOKEN" \
  -d '["GET", "greeting"]'

@capydb/kv

@capydb/kv is a small factory that reads the CapyDB variable names and returns a configured @upstash/redis client. It exists so you do not hand-wire the same three lines in every service; it is not a wrapper, and the object it returns is an @upstash/redis client with nothing added.

npm install @capydb/kv @upstash/redis
import { createKv } from '@capydb/kv'

const kv = createKv()
await kv.set('hello', 'world')

It reads CAPYKV_REST_URL / CAPYKV_REST_TOKEN, falling back to UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN so an existing Upstash deployment keeps working. It refuses a plaintext http:// URL unless you opt in explicitly.

RESP

Ordinary Redis® OSS clients connect over TLS on port 6379, with the token as the password. Blocking commands and pub/sub work here and not over REST.

The RESP URL is published as CAPYKV_REDIS_URL. It embeds the token as its password, so like the token it exists in full only on the create and rotate responses: capydb kv create --write-env and capydb kv rotate-token --write-env put it in your env file next to the REST pair, and the dashboard shows it once, alongside the token. The deployment integrations cannot push it, for the same reason they cannot push the token.

The endpoint routes by TLS server name (SNI). One address serves every store, and the server name in the TLS handshake is what picks yours. A client that connects without one is refused with ERR TLS handshake carried no server name (SNI) … - that is not a hostname or token problem. Node's tls.connect sends no server name unless servername is set, so ioredis, and everything built on it (BullMQ included), needs tls: { servername }; redis-cli and valkey-cli need --sni. redis-py and go-redis send it on their own.

ioredis
import Redis from 'ioredis'

const url = process.env.CAPYKV_REDIS_URL!
const redis = new Redis(url, { tls: { servername: new URL(url).hostname } })
const job = await redis.blpop('queue', 0)
redis-py
import redis

r = redis.from_url(os.environ["CAPYKV_REDIS_URL"])
r.set("hello", "world")
go-redis
opt, err := redis.ParseURL(os.Getenv("CAPYKV_REDIS_URL"))
if err != nil {
    return err
}
client := redis.NewClient(opt)
redis-cli
redis-cli --tls --sni <project>.db.capydb.dev -u "$CAPYKV_REDIS_URL" PING

GET .../kv/credentials returns the RESP endpoint without a password and sets token_required: true, because only the token's hash is stored. If you no longer have the token, rotate.

Commands that are not available

The store's ACL denies the administrative and replication surface. Everything a client application does is allowed; everything that would reconfigure the cell, take over its replication, or read another tenant's traffic is not:

CONFIG, SHUTDOWN, DEBUG, MODULE, REPLICAOF / SLAVEOF, SAVE / BGSAVE / BGREWRITEAOF, ACL, CLUSTER, MIGRATE, SYNC / PSYNC, MONITOR, FAILOVER, RESET.

Persistence and eviction are managed by CapyDB, which is why CONFIG and the SAVE family are not yours to call - see Operations.

Migrating from Upstash

Change two variables. The REST protocol, the client, the limiter and the command surface are the same; what changes is the region the store sits in and the fact that it is billed as part of your plan rather than per command.

If you already read UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN throughout your code, set those names to CapyDB's values and change nothing else.