> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# KV Quick Start

> Get started with Telnyx KV in minutes: create a namespace, bind it to your edge function, and read or write data using the dashboard or API.

Get up and running with KV by creating a namespace, binding it to your function, and reading/writing data.

## 1. Create a Namespace

<Tabs>
  <Tab title="CLI">
    ```bash theme={null}
    telnyx-edge storage kv create --name my-cache
    ```
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    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"}'
    ```
  </Tab>
</Tabs>

<Note>
  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`.
</Note>

## 2. Add to Your Function

Configure the binding in `func.toml`:

```toml theme={null}
[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.

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={null}
    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);
    }
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    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)
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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}
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    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());
            }
        }
    }
    ```
  </Tab>
</Tabs>

<Note>
  A native SDK is planned. Until it ships, use the HTTP endpoints shown above.
</Note>

## Best Practices

### Key Naming

Keys may contain `a-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):

```javascript theme={null}
// 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:

```javascript theme={null}
const value = await kvGet("possibly-missing-key");
if (value === null) {
    // Key doesn't exist
    return new Response("Not found", { status: 404 });
}
```
