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"}'
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 = "kv-abc123" # Storage resource ID from provisioning API
3. Access KV in Your Code
KV can be accessed via HTTP endpoints or SDK (coming soon). Currently, use HTTP requests to the storage API:- JavaScript
- Go
- Python
- Java
const KV_NAMESPACE_ID = process.env.KV_MY_CACHE_ID;
const API_KEY = process.env.TELNYX_API_KEY;
// UTF-8 safe base64 encoding/decoding (handles large payloads)
function toBase64(str) {
const bytes = new TextEncoder().encode(str);
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function fromBase64(b64) {
const binary = atob(b64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return new TextDecoder().decode(bytes);
}
async function kvGet(key) {
const response = await fetch(
`https://api.telnyx.com/v2/storage/kvs/${KV_NAMESPACE_ID}/keys/${encodeURIComponent(key)}`,
{ headers: { "Authorization": `Bearer ${API_KEY}` } }
);
if (response.status === 404) return null; // Key not found
if (!response.ok) throw new Error(`KV error: ${response.status}`);
const data = await response.json();
return fromBase64(data.data.value);
}
async function kvPut(key, value) {
const response = await fetch(
`https://api.telnyx.com/v2/storage/kvs/${KV_NAMESPACE_ID}/keys/${encodeURIComponent(key)}`,
{
method: "PUT",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ value: toBase64(value) })
}
);
if (!response.ok) throw new Error(`KV write error: ${response.status}`);
}
export async function handler(request) {
// Write a value (Unicode safe)
await kvPut("user:123", JSON.stringify({ name: "Alice 👋" }));
// Read a value
const user = await kvGet("user:123");
return new Response(user);
}
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"strings"
)
var (
kvNamespaceID = os.Getenv("KV_MY_CACHE_ID")
apiKey = os.Getenv("TELNYX_API_KEY")
)
func kvGet(key string) (string, error) {
encodedKey := url.PathEscape(key)
req, _ := http.NewRequest("GET",
fmt.Sprintf("https://api.telnyx.com/v2/storage/kvs/%s/keys/%s", kvNamespaceID, encodedKey),
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)
}
var result struct {
Data struct {
Value string `json:"value"`
} `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&result)
decoded, _ := base64.StdEncoding.DecodeString(result.Data.Value)
return string(decoded), nil
}
func kvPut(key, value string) error {
encodedKey := url.PathEscape(key)
encoded := base64.StdEncoding.EncodeToString([]byte(value))
body := fmt.Sprintf(`{"value":"%s"}`, encoded)
req, _ := http.NewRequest("PUT",
fmt.Sprintf("https://api.telnyx.com/v2/storage/kvs/%s/keys/%s", kvNamespaceID, encodedKey),
strings.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
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
import base64
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:
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.
data = response.json()
return base64.b64decode(data["data"]["value"]).decode()
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-Type": "application/json"
},
json={"value": base64.b64encode(value.encode()).decode()}
)
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.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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();
/**
* Encode key for use in URL path segment (RFC 3986).
* Unlike URLEncoder, this encodes spaces as %20 not +.
*/
private static String encodePathSegment(String segment) {
StringBuilder result = new StringBuilder();
for (byte b : segment.getBytes(StandardCharsets.UTF_8)) {
char c = (char) (b & 0xFF);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') {
result.append(c);
} else {
result.append(String.format("%%%02X", b));
}
}
return result.toString();
}
/**
* Get a value from KV. Returns null if key not found.
*/
public String get(String key) throws Exception {
String encodedKey = encodePathSegment(key);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.telnyx.com/v2/storage/kvs/" + namespaceId + "/keys/" + encodedKey))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 404) {
return null; // Key not found
}
if (response.statusCode() != 200) {
throw new RuntimeException("KV error: " + response.statusCode());
}
String base64Value = parseValue(response.body());
return new String(Base64.getDecoder().decode(base64Value), StandardCharsets.UTF_8);
}
/**
* Put a value to KV. Supports Unicode via UTF-8.
*/
public void put(String key, String value) throws Exception {
String encodedKey = encodePathSegment(key);
String encoded = Base64.getEncoder().encodeToString(value.getBytes(StandardCharsets.UTF_8));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.telnyx.com/v2/storage/kvs/" + namespaceId + "/keys/" + encodedKey))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString("{\"value\":\"" + encoded + "\"}"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new RuntimeException("KV write error: " + response.statusCode());
}
}
// Extract "value" field from JSON response: {"data":{"value":"..."}}
private String parseValue(String json) {
Pattern pattern = Pattern.compile("\"value\"\\s*:\\s*\"([^\"]*)\"");
Matcher matcher = pattern.matcher(json);
if (matcher.find()) {
return matcher.group(1);
}
throw new RuntimeException("Could not parse value from response: " + json);
}
}
Native SDK support is coming soon. The SDK will follow the Telnyx client pattern:Currently, access KV via HTTP endpoints as shown above.
import Telnyx from 'telnyx';
const client = new Telnyx({ apiKey: process.env.TELNYX_API_KEY });
// Get a value
const value = await client.storage.kvs.keys.get("kv-abc123", "user:123");
// Put a value with TTL
await client.storage.kvs.keys.put("kv-abc123", "user:123", {
value: "data",
expirationTtl: 3600,
metadata: { type: "session" }
});
Best Practices
Key Naming
Use structured key names with prefixes:user:123 # User data
session:abc # Session data
cache:api:/users # Cached API response
flag:new-feature # Feature flag
Value Serialization
Always serialize complex values as JSON before base64 encoding:// 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 });
}