Telnyx Storage: KV — Full Documentation
Complete page content for KV (Storage section) of the Telnyx developer docs (https://developers.telnyx.com). This file: https://developers.telnyx.com/development/llms/storage-kv-llms-full-txt.md · Root index: https://developers.telnyx.com/llms.txt
Get Started
KV
Source: https://developers.telnyx.com/docs/edge-compute/kv.mdKV is a globally distributed key-value store: you write bytes under a string key and read them back, fast, from anywhere. It is built for read-heavy edge workloads — session data, cached responses, feature flags, and other small values a function needs on every request. A value is opaque bytes. You choose the serialization (text, JSON, binary); KV stores exactly what you send and returns it byte-for-byte. There is no envelope, no base64 encoding, and no server-side interpretation of the value.
Two Ways to Use KV
The same namespaces and keys are reachable two ways. Pick based on where your code runs.
Both hit the same store, so a value written through the binding is immediately readable over REST and vice versa. The binding is a thin, pre-authenticated wrapper over the same REST endpoints — it just means your function never handles an API key. The complete endpoint reference — namespaces and keys, with request/response schemas and code samples — is generated from the OpenAPI spec and lives in the REST API group of this KV section in the sidebar.
The
env KV binding is TypeScript-only and requires @telnyx/edge-runtime ≥ 0.2.2. Go, JS, Python, and Quarkus functions use the REST API directly.
Next Steps
- Quick Start — Create a namespace, bind it, read and write
- How KV Works — Keys, TTL, and the consistency model
- Examples — Session storage, caching, and feature flags
- Runtime API — the
envbinding surface (KvNamespace) - CLI Commands — Manage KV from the command line
- Pricing — Free tier and paid plans
Related Resources
Quick Start
Source: https://developers.telnyx.com/docs/edge-compute/kv/quick-start.mdGet up and running with KV: create a namespace, then use it from a TypeScript function through an
env binding, or from anywhere through the REST API.
1. Create a Namespace
A namespace is an isolated key space. Create one with the CLI or the API.id (a UUID) — you’ll need it in the next step.
A new namespace starts in status: "pending" and isn’t writable yet: writes return 409 ("Namespace is not ready (status: pending)") until provisioning finishes, which typically takes a few seconds and can stretch to ~20. If you’re scripting, poll GET https://api.telnyx.com/v2/storage/kvs/{id} until "status": "provision_ok" before your first write. (With the binding path below you rarely notice — editing func.toml and deploying already takes longer than provisioning.)
Path A: The Function Binding
Recommended for TypeScript edge functions. The runtime injects the credential, so your code holds no API key.2. Bind the Namespace
Declare the namespace infunc.toml. The block key is a name you choose — it’s not a reserved word — and it becomes the property on env. This example uses MY_KV, so the binding is reached as env.MY_KV:
@telnyx/edge-runtime (≥ 0.2.2) to your package.json dependencies, then regenerate the environment types:
[storage.kv.<NAME>] block becomes env.<NAME>: KvNamespace in the generated telnyx-env.d.ts — declare as many namespaces as you need. KV type generation requires CLI ≥ v0.2.3 (earlier releases report the block as an unrecognized key and write an empty Env). The binding itself resolves at runtime from func.toml — types are for the compiler, and a stale telnyx-env.d.ts doesn’t affect the deployed function.
3. Use env.MY_KV in Your Code
list() returns key metadata, not values — { keys: [{ name, sizeBytes, updatedAt }], list_complete, cursor? }. Paginate by passing the returned cursor back in list({ cursor }). On 0.2.1 entries carry only name; 0.2.0 throws a response-shape error.
put’s expirationTtl option requires ≥ 0.2.2 — earlier versions accept it but silently ignore it. The metadata option is deprecated and ignored on every version. See Key Expiration.
Path B: The REST API
Use this anywhere outside a TypeScript edge function — a non-TypeScript function (Go, JS, Python, Quarkus), your own backend, or tooling. Authenticate with yourTELNYX_API_KEY (the SDKs read it from the environment). Whether you use an SDK or plain HTTP, the value is the raw request/response body — no base64, no envelope.
KV support landed in the official server SDKs in telnyx-node ≥ 7.5.0, telnyx-python ≥ 4.166.0, telnyx-php ≥ 7.88.0 (see the PHP tab for the required version pin), telnyx-ruby ≥ 5.152.0, and telnyx-go ≥ v4.85.0 — on earlier versions the storage resource is object storage (buckets) only. The Java SDK doesn’t cover KV yet; call the endpoints over plain HTTP as in the curl tab.
composer require "telnyx/telnyx-php:^7.88" guzzlehttp/guzzle. The pin matters: the semver-highest tag v8.0.0 predates KV, so an unpinned composer require telnyx/telnyx-php installs a version with no storage->kvs at all. Guzzle (or any PSR-18 client) is required because the SDK doesn’t bundle one — without it new Client() throws a discovery exception.
gem install base64 — telnyx 5.152.0 loads it but doesn’t yet declare it as a dependency.
ttl_secs), its error cases, and an inspectable application-level alternative are covered in Key Expiration.
list returns key names and per-key metadata, never values:
meta.has_more is true, pass the returned meta.cursor back as ?cursor= (in the SDKs, the cursor parameter) to fetch the next page — key listing does not auto-paginate in any SDK.
Inside an edge function, the org binding injects TELNYX_API_KEY (and a base-URL proxy) at runtime, so REST calls from a function authenticate without you shipping a key.
Next: Best Practices for key naming, serialization, and error handling.
Concepts
How KV Works
Source: https://developers.telnyx.com/docs/edge-compute/kv/concepts/how-kv-works.mdKV is a single global key-value store optimized for low-latency reads from edge functions. A value is opaque bytes — you choose the serialization (text, JSON, binary), and KV stores exactly what you send and returns it byte-for-byte, with no envelope, base64 encoding, or server-side interpretation.
Keys
A key is a path-like string. Allowed characters area-z, A-Z, 0-9, and - _ / = .. Use / to group related keys (for example user/123, session/abc). Colons (:) are not allowed.
Expiration (TTL)
By default a value lives until you delete it. You can also set a server-side TTL so a key expires automatically: passexpirationTtl on a binding put (env.MY_KV.put(key, value, { expirationTtl: 30 }), requires @telnyx/edge-runtime ≥ 0.2.2), ttl_secs on a REST write (PUT …/keys/{key}?ttl_secs=N), or --ttl on the CLI (telnyx-edge storage kv key put … --ttl 30s). The TTL is a whole number of seconds; once it elapses the key is gone and reads return null/404. See Key Expiration.
No Per-Key Metadata
KV has no per-key metadata. The binding’sput accepts a metadata option so older code keeps compiling, but it is ignored (deprecated as of 0.2.2), and list never returns metadata.
Consistency and Regionality
KV is a single global store. There is no region to choose at creation time and no per-region copies to reconcile — every namespace is one logical dataset reachable from every edge location. Writes are replicated for durability and committed by quorum before they’re acknowledged.- Read-your-writes from a given location is reliable: once a write returns, a subsequent read sees it.
- Across locations, a read issued immediately after a write elsewhere may briefly observe the previous value; treat cross-location visibility as near-real-time rather than instantaneous.
- Distance costs latency, not staleness — a location far from where the data is coordinated pays network round-trip on the request, but reads the same authoritative data as everywhere else.
- No transactions or compare-and-swap. Don’t use KV for atomic read-modify-write, counters, or coordination — concurrent writers to one key are last-write-wins.
Key Expiration
Source: https://developers.telnyx.com/docs/edge-compute/kv/ttl-and-metadata.mdKV 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
PassexpirationTtl 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.
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.
No Per-Key Metadata
KV stores values as opaque bytes and has no per-key metadata. The binding’sput 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 absoluteexpires_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).
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.
Examples
Session Storage
Source: https://developers.telnyx.com/docs/edge-compute/kv/examples/session-storage.mdStore user sessions at the edge and expire them after a day. This uses the KV binding (bound as
MY_KV) with a server-side TTL: pass expirationTtl on the write and KV deletes the session automatically once it elapses. Each write renews the TTL, so an active session slides forward and an abandoned one expires. Requires @telnyx/edge-runtime ≥ 0.2.2.
ttl_secs parameter on a REST API write. If you need to inspect when a session expires, use the application-level envelope instead — see Key Expiration.
API Response Caching
Source: https://developers.telnyx.com/docs/edge-compute/kv/examples/api-response-caching.mdCache expensive upstream responses for a few minutes. This uses the KV binding (bound as
MY_KV) with a server-side TTL: pass expirationTtl on the write and KV deletes the key automatically once it elapses — no cleanup code. Requires @telnyx/edge-runtime ≥ 0.2.2.
ttl_secs parameter on a REST API write. If you need to inspect when an entry expires, use the application-level envelope instead — see Key Expiration.
Feature Flags
Source: https://developers.telnyx.com/docs/edge-compute/kv/examples/feature-flags.mdRead flags on the request path — no expiry needed, so use the KV binding (
env.MY_KV) directly.
Reference
Overview
Source: https://developers.telnyx.com/docs/edge-compute/kv/reference.mdThe types in this reference are exported from
@telnyx/edge-runtime (TypeScript) and describe version ≥ 0.2.2 — the first release where expirationTtl is applied and list() entries carry sizeBytes/updatedAt. They describe the env binding — the in-function surface. To read or write KV from another language or outside a function, use the REST API.
The KV Runtime API is a single binding type and the small set of option/result types its methods take. A namespace declared as [storage.kv.<NAME>] in func.toml resolves on env.<NAME> as a KvNamespace.
Getting the Binding
func.toml; run telnyx-edge types (CLI ≥ v0.2.3) after editing the manifest to regenerate telnyx-env.d.ts, which types env.<NAME> as a KvNamespace.
Related Resources
KvNamespace— the method-by-method reference- Bindings — how bindings resolve on
env - REST API — the same operations over HTTP
- Key Expiration — server-side TTL via
expirationTtl,ttl_secs, or--ttl
KvNamespace
Source: https://developers.telnyx.com/docs/edge-compute/kv/reference/kv-namespace.md
env.<BINDING> (a KvNamespace) is the in-function handle to a KV namespace. It’s a thin, pre-authenticated wrapper over the KV REST API — the runtime injects the credential, so your code holds no API key.
- Values are opaque bytes —
putstores the string you pass verbatim (no base64, no envelope);getreturns it byte-for-byte. - Missing keys read as
null—getresolves tonullfor a key that doesn’t exist, not an error. deleteis idempotent — deleting a missing key succeeds.- Read-your-writes — a read after a successful
putfrom the same location reflects it. See Consistency. - Errors throw — a non-2xx from the store (other than the
404→nullonget) rejects the promise with anErrordescribing the operation and status.
get(key, options?)
Read a value. Two overloads, selected by options.type:
null if the key does not exist. With { type: "json" }, a malformed stored value throws from JSON.parse.
put(key, value, options?)
Write a value. value is a string, stored verbatim. Resolves once the write is acknowledged.
expirationTtl maps to the REST API’s ?ttl_secs= parameter: the key is deleted server-side roughly that many seconds after the write. The value is floored to a whole number of seconds; anything below 1 is not sent — the write succeeds without a TTL. See Key Expiration.
expirationTtl requires @telnyx/edge-runtime ≥ 0.2.2 — earlier versions accept it but silently ignore it. metadata is ignored on every version (KV has no per-key metadata); it remains on the type, deprecated, so code that sets it keeps compiling.
delete(key)
Remove a key. Idempotent — deleting a missing key resolves normally.
list(options?)
Enumerate keys (names only — list does not return values).
list() requires @telnyx/edge-runtime ≥ 0.2.1 — on 0.2.0 it throws Unexpected KV list response shape, because that release’s parser predates the current API list format. sizeBytes and updatedAt are populated from 0.2.2; on 0.2.1 entries carry only name.
KvKeyInfo.metadata is never populated — KV has no per-key metadata. It remains on the type, deprecated, so code that reads it keeps compiling.
Related
- Overview — the KV Runtime API surface at a glance
- Quick Start — declare the binding and type it
- REST API — the same operations over HTTP
CLI
Source: https://developers.telnyx.com/docs/edge-compute/kv/cli.mdManage KV namespaces and keys using the
telnyx-edge CLI.
Namespace Management
Key Operations
The value is stored verbatim — pass it as a positional argument, or use--path to store the contents of a file.
Key Put Flags
Key List Flags
Keys may contain
a-z, A-Z, 0-9, and - _ / = . (no colons). Use --ttl for server-side expiry (see Key Expiration). KV has no per-key metadata, so there is no --metadata flag.
Best Practices
Source: https://developers.telnyx.com/docs/edge-compute/kv/best-practices.mdPractical guidance for working with KV, whether through the
env binding or the REST API.
Key Naming
Keys may containa-z, A-Z, 0-9, and - _ / = . (no colons). Use / to group related keys:
list({ prefix: "user/" }) (or ?prefix=user/ over REST).
Value Serialization
KV stores values verbatim, so serialize complex values yourself (no base64 needed):Missing Keys
get returns null for a key that doesn’t exist — handle it explicitly:
Keep Values Small
KV is built for many small values read on the request path, not for large blobs. A value is capped at 1 MiB (1,048,576 bytes) — a larger write is rejected with413. Store big or binary objects in Cloud Storage and keep only the key or a small reference in KV.
Limits
Don’t Rely on Atomicity
KV has no transactions or compare-and-swap, and concurrent writers to one key are last-write-wins. Don’t use it for counters, locks, or coordination — see Consistency.Platform
Pricing
Source: https://developers.telnyx.com/docs/edge-compute/kv/pricing.mdKV pricing is based on operations and storage. Egress is free.
Pricing
Egress is free. No charges for data transferred out of KV.
API Reference (KV)
kv namespaces
- List KV namespaces: Lists the KV namespaces for the authenticated user’s organization. Results use page-based pagination (
page[number]/page[size]). - Create a KV namespace: Creates a new KV namespace. Provisioning is asynchronous: the namespace is returned with status
pendingand becomes usable once it reachesprovision_ok. - Get a KV namespace: Retrieves a KV namespace by its ID, including its provisioning status.
- Delete a KV namespace: Deletes a KV namespace and all of the keys it contains. Deletion is asynchronous: the namespace is returned with status
deleting. Deleting a namespace whose…
kv keys
- List keys: Lists the keys in a namespace. Returns key names and metadata only, never values. Results are paginated with
limitand an opaquecursor. - Get a key’s value: Returns the raw stored value for a key. The response body is the value exactly as it was written; the
Content-Typeheader echoes the value’s stored content t… - Set a key’s value: Creates or replaces the value for a key. The request body is stored verbatim as the value — no base64, no JSON envelope — up to 1 MiB. The request’s `Content-T…
- Delete a key: Deletes a key. Idempotent: deleting a key that does not exist still succeeds. The namespace itself must exist and be provisioned.
cloudfs filesystems
- List CloudFS filesystems: Lists the CloudFS filesystems for the authenticated user’s organization. Results use cursor-based pagination: fetch the next page by passing `meta.cursors.afte…
- Create a CloudFS filesystem: Creates a CloudFS filesystem. Provisioning is synchronous — typically a few seconds, up to a few minutes — and the filesystem is returned with status
ready,… - Get a CloudFS filesystem: Retrieves a CloudFS filesystem by its ID. The returned
meta_urlomits the credential — the metadata token is only ever returned by create and rotate-meta-tok… - Update a CloudFS filesystem: Updates a CloudFS filesystem. Only
namecan be changed; other fields are immutable and unknown fields are rejected with a400. Renaming to a name that alre… - Delete a CloudFS filesystem: Permanently deletes a CloudFS filesystem, removing its S3 bucket and its metadata database. Deletion is synchronous: the response returns the filesystem’s fina…
- Rotate the metadata token: Issues a new metadata access token for the filesystem and returns the full filesystem, including the new
meta_tokenand credential-bearingmeta_url. The pr…