1. Create a Namespace
- CLI
- API
telnyx-edge storage kv create --name my-cache
curl -X POST https://api.telnyx.com/v2/storage/kvs \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "my-cache"}'
The namespace
name may contain only lowercase letters, numbers, and hyphens. Namespace creation is asynchronous — poll the namespace endpoint until status changes from pending to provision_ok.2. Add to Your Function
Configure the binding infunc.toml:
[edge_compute]
func_name = "my-function"
[storage.kv.MY_CACHE]
id = "550e8400-e29b-41d4-a716-446655440000" # Namespace ID returned by the create API (a UUID)
3. Access KV in Your Code
KV is accessed over HTTP (a native SDK is planned). A value is stored and returned verbatim — there is no base64 encoding and no JSON envelope, so you send the raw value as the request body and read the raw bytes back.- JavaScript
- Go
- Python
- Java
const KV_NAMESPACE_ID = process.env.KV_MY_CACHE_ID;
const API_KEY = process.env.TELNYX_API_KEY;
const BASE = `https://api.telnyx.com/v2/storage/kvs/${KV_NAMESPACE_ID}/keys`;
async function kvGet(key) {
const res = await fetch(`${BASE}/${encodeURIComponent(key)}`, {
headers: { "Authorization": `Bearer ${API_KEY}` }
});
if (res.status === 404) return null; // Key not found
if (!res.ok) throw new Error(`KV read error: ${res.status}`);
return await res.text(); // Value is the raw stored bytes
}
async function kvPut(key, value) {
const res = await fetch(`${BASE}/${encodeURIComponent(key)}`, {
method: "PUT",
headers: { "Authorization": `Bearer ${API_KEY}` },
body: value // Stored verbatim — no base64, no envelope
});
if (!res.ok) throw new Error(`KV write error: ${res.status}`);
}
export async function handler(request) {
// Write a value (UTF-8 is preserved as-is)
await kvPut("user/123", JSON.stringify({ name: "Alice 👋" }));
// Read it back -> '{"name":"Alice 👋"}'
const user = await kvGet("user/123");
return new Response(user);
}
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)
var (
kvNamespaceID = os.Getenv("KV_MY_CACHE_ID")
apiKey = os.Getenv("TELNYX_API_KEY")
)
// keys may contain "/" as a separator; escape each segment individually
func kvURL(key string) string {
segs := strings.Split(key, "/")
for i, s := range segs {
segs[i] = url.PathEscape(s)
}
return fmt.Sprintf("https://api.telnyx.com/v2/storage/kvs/%s/keys/%s",
kvNamespaceID, strings.Join(segs, "/"))
}
func kvGet(key string) (string, error) {
req, _ := http.NewRequest("GET", kvURL(key), nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode == 404 {
return "", nil // Key not found
}
if resp.StatusCode >= 400 {
return "", fmt.Errorf("KV read error: %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
return string(body), nil // Raw stored bytes
}
func kvPut(key, value string) error {
req, _ := http.NewRequest("PUT", kvURL(key), strings.NewReader(value)) // verbatim
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("KV write error: %d", resp.StatusCode)
}
return nil
}
func handler(w http.ResponseWriter, r *http.Request) {
kvPut("user/123", `{"name": "Alice 👋"}`)
user, _ := kvGet("user/123")
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, user)
}
import os
from urllib.parse import quote
import httpx
KV_NAMESPACE_ID = os.getenv("KV_MY_CACHE_ID")
API_KEY = os.getenv("TELNYX_API_KEY")
BASE_URL = f"https://api.telnyx.com/v2/storage/kvs/{KV_NAMESPACE_ID}/keys"
async def kv_get(key: str) -> str | None:
async with httpx.AsyncClient() as client:
# keep "/" literal so it stays a key separator
response = await client.get(
f"{BASE_URL}/{quote(key, safe='/')}",
headers={"Authorization": f"Bearer {API_KEY}"}
)
if response.status_code == 404:
return None # Key not found
response.raise_for_status() # Raise on 401, 429, 5xx, etc.
return response.text # Raw stored bytes
async def kv_put(key: str, value: str) -> None:
async with httpx.AsyncClient() as client:
response = await client.put(
f"{BASE_URL}/{quote(key, safe='/')}",
headers={"Authorization": f"Bearer {API_KEY}"},
content=value.encode() # Stored verbatim — no base64, no envelope
)
response.raise_for_status() # Raise on non-2xx
class Function:
async def handler(self, request):
await kv_put("user/123", '{"name": "Alice 👋"}')
user = await kv_get("user/123")
return {"body": user}
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class KVClient {
private final String namespaceId = System.getenv("KV_MY_CACHE_ID");
private final String apiKey = System.getenv("TELNYX_API_KEY");
private final HttpClient client = HttpClient.newHttpClient();
// Keys may contain "/" as a separator; encode each segment, keeping "/" literal.
private String url(String key) {
StringBuilder sb = new StringBuilder();
for (String seg : key.split("/", -1)) {
if (sb.length() > 0) sb.append('/');
sb.append(URLEncoder.encode(seg, StandardCharsets.UTF_8).replace("+", "%20"));
}
return "https://api.telnyx.com/v2/storage/kvs/" + namespaceId + "/keys/" + sb;
}
/** Get a value. Returns null if the key does not exist. */
public String get(String key) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url(key)))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (response.statusCode() == 404) {
return null; // Key not found
}
if (response.statusCode() >= 400) {
throw new RuntimeException("KV read error: " + response.statusCode());
}
return response.body(); // Raw stored bytes
}
/** Put a value, stored verbatim (UTF-8). */
public void put(String key, String value) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url(key)))
.header("Authorization", "Bearer " + apiKey)
.PUT(HttpRequest.BodyPublishers.ofString(value, StandardCharsets.UTF_8))
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new RuntimeException("KV write error: " + response.statusCode());
}
}
}
A native SDK is planned. Until it ships, use the HTTP endpoints shown above.
Best Practices
Key Naming
Keys may containa-z, A-Z, 0-9, and - _ / = . (no colons). Use / to group related keys:
user/123 # User data
session/abc # Session data
cache/api/users # Cached API response
flag/new-feature # Feature flag
Value Serialization
KV stores values verbatim, so serialize complex values yourself (no base64 needed):// Write
const value = JSON.stringify({ name: "Alice", age: 30 });
await kvPut("user/123", value);
// Read
const user = JSON.parse(await kvGet("user/123"));
Error Handling
Handle missing keys gracefully:const value = await kvGet("possibly-missing-key");
if (value === null) {
// Key doesn't exist
return new Response("Not found", { status: 404 });
}