---
title: KV
description: Low-latency key-value storage with TTLs, counters, hashes, lists, sorted sets and compare-and-set, served at api.data.sylphx.com.
type: tutorial
product: kv
summary: Create a namespace, write and read a value, and where KV is served.
updated: 2026-10-01
order: 0
---

KV stores bytes under keys, grouped in a **namespace** you create. A value can
expire on a timer, a counter can be incremented atomically, and a write can be
made conditional. Beyond plain values, a key can hold a hash, a list or a
sorted set.

## Where KV is served

<Callout tone="note" title="KV is served on its own host">
KV is not served on the one API at `api.sylphx.com`. Its key-value calls, and the
call that creates a namespace, are served by Sylphx Data at
`https://api.data.sylphx.com`, with the same Sylphx key. The `kv_namespaces`
collection in the API reference and the `sylphx data kv-namespaces` commands
are not routed on `api.sylphx.com`, so use the calls on this page.
</Callout>

A key needs the `data:write` scope to write and `data:read` to read. Every
call that changes data carries a `Sylphx-Effect-Id` header, 1 to 128 characters
of letters, digits and `. _ : -`, new for each new intent and reused on a
retry: Data then applies a retried write once. The `@sylphx/sdk` client sets it
for you.

## Create a namespace

```bash
export DATA_URL="https://api.data.sylphx.com/v1"

curl -X POST "$DATA_URL/resources" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Sylphx-Effect-Id: create-kv-demo-1" \
  -d '{
    "resource": {
      "id": "kv-demo",
      "name": "kv-demo",
      "kind": "RESOURCE_KIND_KV_NAMESPACE",
      "kvNamespace": { "defaultTtlSeconds": "3600" }
    }
  }'
```

`defaultTtlSeconds` is the lifetime of a value written without its own TTL.
The body is ProtoJSON, so 64-bit integers are strings.

## Write and read a value

<CodeTabs>
	<CodeTab
		label="cURL"
		language="bash"
		code={`curl -X PUT "$DATA_URL/kv/kv-demo/greeting" \\
  -H "Authorization: Bearer $SYLPHX_API_KEY" \\
  -H "Content-Type: application/json" \\
  -H "Sylphx-Effect-Id: put-greeting-1" \\
  -d '{"namespaceId": "kv-demo", "key": "greeting", "value": "aGVsbG8=", "ttlSeconds": "60"}'

curl "$DATA_URL/kv/kv-demo/greeting" \\
  -H "Authorization: Bearer $SYLPHX_API_KEY"`}
	/>
	<CodeTab
		label="TypeScript"
		language="ts"
		code={`import { Sylphx } from '@sylphx/sdk'

const sylphx = new Sylphx()

// The value is base64: 'aGVsbG8=' is "hello".
await sylphx.data.kv.put({
  namespaceId: 'kv-demo',
  key: 'greeting',
  value: Buffer.from('hello').toString('base64'),
  ttlSeconds: '60',
})

const { value } = await sylphx.data.kv.get({ namespaceId: 'kv-demo', key: 'greeting' })
console.log(Buffer.from(value?.value ?? '', 'base64').toString())`}
	/>
</CodeTabs>

The answer is the stored entry: the `value`, its `sha256`, a `version` that
increases on every write, and `expiresAtUnixMs` (0 never expires). A missing or
expired key answers `404`.

## What you can do

| Need | Call |
| --- | --- |
| Set a lifetime | `ttlSeconds` on the write; `0` never expires; `expire` changes it later. |
| Write only if the key is absent or present | `nx` or `xx` on the write. |
| Write only if nobody else wrote since you read | `expectedVersion` on the write. |
| Count | `increment` adds `delta` (default 1) to an integer value. |
| Read many | `get_many` reads up to 1000 keys in one call. |
| Group fields under one key | `hash_set`, `hash_get`, `hash_get_all`, `hash_get_many`. |
| Queue or stack | `list_push`, `list_range`, `list_pop`, `list_length`. |
| Rank | `zset_add`, `zset_range`, `zset_score`, `zset_length`. |
| Walk keys | `list` in key order, or `scan` with a glob and a cursor. |

Each is one method of the [KV collection](/docs/api/kv), with its request,
response and examples.

KV is a key-value store. It does not provide a rate-limiter helper: build one
from `increment` and a TTL.

<RelatedDocs
	links={[
		{
			href: '/docs/api/kv',
			label: 'The KV collection',
			description: 'Every method with its path, scope, request and response.',
		},
		{
			href: '/docs/database',
			label: 'Database',
			description: 'When your data is relational: one Postgres per database.',
		},
	]}
/>
