Language-neutral

API clients

Trestle speaks ordinary JSON over HTTP. Use native fetch directly, start from the small TypeScript reference client, or call the same routes from any server language.

Choose the credential for the caller

Browser code should use a short-lived application-user access token. Trusted servers can use a narrowly scoped service credential. Administrator cookies and service secrets do not belong in public JavaScript.

Browser fetch

Modern browsers provide fetch without another dependency. Serve the frontend and Trestle routes from one origin, then call the route directly.

const response = await fetch(
  "/api/v1/collections/issues/records?limit=25",
  { headers: { Authorization: `Bearer ${token}` } },
);

if (!response.ok) throw new Error("Could not load issues");
const page = await response.json();

console.log(page.items);

Use an application-user access token in browser code. Server-side JavaScript can make the same request with a private Trestle URL and a scoped service credential.

TypeScript reference client

The repository includes clients/typescript/trestle.ts, a deliberately small typed wrapper over fetch. Copy or adapt it when you want record envelopes and errors typed without adopting a large SDK.

const trestle = new TrestleClient(
  "https://api.example.com",
  accessToken,
);

const page = await trestle.list("issues", "?limit=25");
const issue = await trestle.create("issues", {
  title: "Investigate latency",
});

The wrapper encodes collection names, sends JSON and raises an error carrying both the original response and Trestle error body. It does not hide routes, rules or pagination, so replacing it later remains straightforward.

Python requests

Server-side Python can keep the service credential in an environment variable or secret manager. Use a session to apply common headers and reuse connections.

import os
import requests

base_url = "https://api.example.com"
client = requests.Session()
client.headers.update({
    "Accept": "application/json",
    "Authorization": f"Bearer {os.environ['TRESTLE_TOKEN']}",
})

response = client.get(
    f"{base_url}/api/v1/collections/issues/records",
    params={"limit": 25, "sort": "-updatedAt"},
    timeout=10,
)
response.raise_for_status()
issues = response.json()["items"]

Always set a timeout in server code. params handles query encoding, while raise_for_status() prevents an error envelope from being treated as a record page.

Go net/http

Go's standard library is enough. Reuse one http.Client, give every request a context deadline, and close response bodies.

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

endpoint := baseURL + "/api/v1/collections/issues/records?limit=25"
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
    return err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+token)

response, err := client.Do(req)
if err != nil {
    return err
}
defer response.Body.Close()

if response.StatusCode/100 != 2 {
    return decodeTrestleError(response.Body)
}
return json.NewDecoder(response.Body).Decode(&page)

Decode Trestle's error object before returning a generic status error so logs retain its stable code and request ID.

Keep the contract visible

Every client above uses the same routes, bearer tokens, record envelopes, limits and error codes. A wrapper should improve ergonomics without pretending it can bypass access rules, gain hidden operations or infer copy-once secrets. Consult the HTTP API reference for route discovery and the Records page for complete fetch examples.