Key-Value store

A managed key-value cache in every project, reachable over HTTP: read, write, delete, and atomically increment opaque values, with optional per-key TTLs. Server-side only — it answers to your project's secret key and to nothing else.

GET     /kv/v1/{key}
PUT     /kv/v1/{key}
DELETE  /kv/v1/{key}
POST    /kv/v1/{key}/incr
Gated per project. The KV store is off by default on every project. Ask us to enable it and we flip the per-project switch; until then every KV request answers 404, even with a valid key.

What it is

The store lives inside your own project database, in a kv schema your project owns — no separate service to provision, no extension to install, nothing to keep in sync. Values are opaque bytes: whatever you PUT is what you GET back, byte for byte. The store never parses a value, with the single exception of incr, which reads its counter as a base-10 integer.

It is a cache, not a system of record. The backing table is unlogged, which keeps a cache workload's write traffic off the cluster's write-ahead log — at the documented cost that the table is emptied if the database server ever crashes and recovers. Keep anything you cannot regenerate in an ordinary table.

Watch your consumption on the project's Usage tab in the dashboard: the key count and stored bytes are shown against your plan's budgets, sampled hourly.

Authentication

Every KV request must carry your project's secret key (kbs_…) as a bearer token:

Authorization: Bearer kbs_<your-secret-key>

Nothing else is accepted. A publishable key (kbp_…), an end-user access token from Auth, and an unknown or revoked credential all get 401. There is no per-key or per-user policy over KV the way Row-Level Security covers your tables, so the store is deliberately reachable only by callers you trust completely.

Server-side only. The secret key bypasses every policy in your project — never ship it to a browser, a mobile app, or any other client you do not control. Call KV from your own backend, or from an Edge Function.

Keys

A key is a single URL path segment: non-empty and at most 512 bytes, percent-encoded in the URL. An empty or over-long key is a 400. Because the key is one segment, use a separator that is not / when you namespace keys — : is the usual choice, as in session:abc.

Read a value

GET /kv/v1/{key} returns the stored bytes with Content-Type: application/octet-stream, or 404 when the key does not exist or has expired.

curl https://<project-ref>.kethosbase.com/kv/v1/greeting \
  -H "Authorization: Bearer kbs_<your-secret-key>"

hello

Write a value

PUT /kv/v1/{key} stores the request body as the value and answers 204 No Content. Writing an existing key overwrites it.

curl -X PUT https://<project-ref>.kethosbase.com/kv/v1/greeting \
  -H "Authorization: Bearer kbs_<your-secret-key>" \
  --data-binary 'hello'

Add a TTL with the Kv-Ttl-Seconds header — a non-negative integer number of seconds:

curl -X PUT https://<project-ref>.kethosbase.com/kv/v1/session:abc \
  -H "Authorization: Bearer kbs_<your-secret-key>" \
  -H "Kv-Ttl-Seconds: 300" \
  --data-binary '{"user":"u_123"}'
Kv-Ttl-SecondsEffect
absentThe key never expires. This is the default.
0The key expires immediately — a subsequent read is a 404.
N > 0The key expires N seconds after the write.
anything else400 — a negative or non-integer value is refused.

Each write sets the TTL afresh: a PUT without the header on a key that had one clears its expiry. An expired key stops being readable the moment it lapses; the space it held is reclaimed by a background sweep, which also releases it from your budgets.

Delete a key

DELETE /kv/v1/{key} removes the key and reports whether it was there. It is idempotent — deleting a missing key is a success, not an error.

curl -X DELETE https://<project-ref>.kethosbase.com/kv/v1/greeting \
  -H "Authorization: Bearer kbs_<your-secret-key>"

{"deleted":true}

Increment a counter

POST /kv/v1/{key}/incr atomically adds a delta to the integer counter stored at the key and returns the running total. The key is created at zero first, so you never have to seed a counter. The update happens in a single statement, so concurrent callers never lose an increment.

curl -X POST https://<project-ref>.kethosbase.com/kv/v1/hits/incr \
  -H "Authorization: Bearer kbs_<your-secret-key>"

{"value":1}

curl -X POST https://<project-ref>.kethosbase.com/kv/v1/hits/incr \
  -H "Authorization: Bearer kbs_<your-secret-key>" \
  -d '{"by":4}'

{"value":5}

The body may be a JSON object {"by": N}, a raw base-10 integer, or empty (which means +1). N may be negative, so incr decrements too. The counter is stored as its base-10 text form, which means a plain GET reads it back as digits.

incr never sets a TTL. Incrementing a key whose TTL has already lapsed restarts the counter at zero and clears the stale expiry. Incrementing a key whose value is not an integer is a 400.

Errors

Errors use the same JSON body as the REST APImessage, code, details, hint — with a KV… code:

{"message":"key not found","code":"KV404","details":null,"hint":null}
StatusCodeWhen
400KV400Empty or over-long key, a malformed Kv-Ttl-Seconds, or a malformed incr body.
400a 22… codeincr on a key whose stored value is not an integer.
401KV401Missing bearer token, or a credential that is not a live secret key for this project.
404KV404The key does not exist or has expired — or KV is not enabled for the project. The two are deliberately indistinguishable to a caller.
413KV413The value is larger than your plan's maximum value size.
429KV429The project spent its monthly request or egress allowance.
503KV503The project is being upgraded; retry shortly.
507KV507The project reached its key-count or storage budget. The message says which.

A 507 fails the write closed rather than evicting something of yours to make room: nothing you stored is ever silently discarded to admit a new key. Delete keys, add TTLs so they retire themselves, or move to a larger plan.

Quotas

Three ceilings apply per project. Max value size bounds a single value (413); keys bounds how many live keys the project holds and storage bounds their total on-disk footprint (both 507).

PlanMax value sizeKeysStorage
Starter256 KiB10,00010 MiB
Pro256 KiB1,000,000256 MiB
Business256 KiB10,000,0002 GiB
Enterprise256 KiB100,000,00016 GiB

Enterprise ceilings are a starting point, not a wall — talk to us if your workload needs more.

The storage figure is the physical footprint of the store — values, keys, the index, and the database's own per-row overhead — not the sum of your value lengths, so expect it to run somewhat ahead of the bytes you wrote. Expired keys still occupy space, and still count, until the hourly sweep reclaims them.

KV requests are ordinary data-API requests: they count toward your project's request allowance and daily ceiling, and the bytes a GET returns count as egress. See Limits & errors.

Limits