KV supports server-side expiration (TTL): set a TTL on a write and the key is deleted automatically once it elapses. Without a TTL, a value lives until you delete it. KV has no per-key metadata.
Server-Side TTL
Pass expirationTtl on a binding put, a ttl_secs query parameter on a REST write, or --ttl on the CLI. The value is a whole number of seconds (1–9223372036); the key expires roughly that many seconds after the write, after which reads return null/404.
An invalid ttl_secs (non-integer, 0, or negative) is rejected with 422 and the key is not written. The binding never produces that 422: it floors expirationTtl to a whole number of seconds and, if the result is less than 1, sends no TTL at all — the write succeeds and the key does not expire. There is no way to read the remaining TTL back — a get/list on a live key does not report its expiry.
expirationTtl requires @telnyx/edge-runtime ≥ 0.2.2. Earlier versions accept the option but silently ignore it — the key is written without a TTL.
KV stores values as opaque bytes and has no per-key metadata. The binding’s put still accepts a metadata option so older code keeps compiling, but it is ignored (and marked @deprecated as of @telnyx/edge-runtime 0.2.2), and list never returns metadata. Anything you put in the value itself (including JSON that looks like those fields) is stored verbatim, not interpreted.
Application-Level Expiry
Server-side TTL deletes the key and tells you nothing else — there is no way to read a key’s remaining lifetime. Use this pattern instead when you want an absolute expires_at timestamp you can inspect on read (or when you’re pinned to @telnyx/edge-runtime < 0.2.2, where expirationTtl is ignored). Wrap your value with the timestamp and check it when you read; if it’s in the past, treat the key as missing (and optionally delete it).
Using the REST API instead? You’d normally reach for ttl_secs above. Build this envelope on top of the REST API examples from the Quick Start only when you need the inspectable expires_at.
Notes on this pattern:
- Reads do the enforcing. An expired key still occupies storage until it’s read (and lazily deleted) or you delete it explicitly. Prefer native TTL (
expirationTtl/ttl_secs) for eager server-side cleanup; if you do need a sweep, drive it from an external scheduler hitting your function over HTTP — HTTP is the only function trigger today.
- Use a consistent clock.
Date.now() on the edge node is fine for coarse expiry; don’t rely on it for sub-second precision.
- Keep the envelope small. You pay for stored bytes, so the wrapper adds a little overhead per key.