WREN Tutorial

Three building blocks — documents, versions, and trees — and everything built on top of them: labels, schemas, binary assets, API keys, collaborators, and permissions. A complete walkthrough of the Admin UI and CLI.

Focused on trees? If you’re using WREN primarily as hierarchical storage — events, catalogues, taxonomies — the tree tutorial is a shorter tree-first walkthrough.

1 Getting started

WREN runs as a single Docker container backed by Postgres. Start everything with:

bash
docker compose up -d
# Admin UI → http://localhost:4000/admin
# API docs  → http://localhost:4000/docs

Login & account

Open http://localhost:4000/admin. On first visit, sign up with an email and password. Every subsequent visit requires the same credentials. Your session persists across browser refreshes.

bashCLI
wren auth login -e [email protected] -p yourpassword
wren auth whoami
wren auth logout
httpREST
POST /api/auth/sign-in/email
{"email":"[email protected]","password":"…"}

GET /api/auth/get-session
WREN login screen

Organisations

When you sign up, WREN creates an organisation for you. Every collection, document, and tree belongs to an organisation — data is fully isolated between orgs. If you're a member of multiple orgs, a dropdown in the sidebar lets you switch the active one.

bashCLI
wren org current
wren org switch <orgId>
httpREST
GET /api/org
PUT /api/org  {"orgId":"<id>"}

2 Three building blocks

Before you dive in, it helps to see the whole shape. WREN is built on three composable primitives. Each one is useful alone, and they all come together in the same API.

Documents (in collections)

A document is a JSON object. Documents live in named collections — schema-free by default, or validated by a JSON Schema when you’re ready. Binary assets (images, PDFs, videos) are first-class and live in collections too. Everything in WREN ultimately hangs off a document.

httpREST
POST /api/v1/events
{ "name": "Masters 2026", "city": "Augusta" }
→ { "id": "abc", "version": 1, ... }

Covered in section 3, with binary assets in section 4.

Versions (and labels)

Every mutation creates a new version — the old data is never overwritten. Roll back, diff any two versions, or pin a label like published or draft to serve a specific snapshot. Labels are how you build publish workflows without splitting your data across two collections.

httpREST
PUT /api/v1/events/abc   →   version 2
POST /api/v1/events/abc/labels { "label": "published" }
GET  /api/v1/events/abc?label=published   →   served at v2

Covered in section 5 and section 6.

Trees (paths pointing at documents)

A tree is a named hierarchy of paths. Each path can point to a document in any collection, be an empty folder, or both. Trees give your data shape — a site map, an event calendar, a product catalogue — and let you fetch a whole branch in one request with ?full=true. Combined with public permissions, a tree becomes a zero-config read API at /orgs/{slug}/tree/….

httpREST
PUT /api/v1/tree/site/2026/masters { "documentId": "abc" }
GET /api/v1/tree/site?full=true&label=published

Covered in section 8.

How they fit together. Documents store the data, versions keep the history, trees give the structure. You can use any one without the others — a schema-free collection with no labels or trees is a valid WREN app. But when you need them, each layer composes cleanly with the rest.

3 Collections & documents

A collection is a named bucket for JSON documents. Collections are listed in the sidebar with their document counts.

Create a collection

Click New collection on the Collections page, enter a name (lowercase, letters, numbers, hyphens, underscores), and click Create. A default open JSON Schema ({"type":"object","additionalProperties":true}) is created automatically so the collection appears in the schema tab immediately.

💡 You can also create a collection implicitly by posting the first document to it via the API — no schema will be set in that case.
bashCLI
# Schema set implicitly on first doc
wren create pages '{"title":"Hello"}'

# Or set schema explicitly first
wren schema set pages '{
  "type":"object",
  "additionalProperties":true
}'
httpREST
PUT /pages/_schema
{
  "collectionType": "json",
  "schema": {
    "type": "object",
    "additionalProperties": true
  }
}
Collections list in the admin UI

Create a document

  1. 1 Open a collection from the sidebar.
  2. 2 Click New document.
  3. 3 Optionally enter a custom ID (leave blank to auto-generate a UUID).
  4. 4 Paste or type your JSON and click Create.
🛡️ If the collection has a strict JSON Schema, invalid documents are rejected with a 422 and a list of field errors.
bashCLI
wren create pages '{"title":"Hello World","published":false}'
httpREST
POST /pages
{"title":"Hello World","published":false}

→ {"id":"abc123","version":1,"data":{…}}
Document list for the articles collection

Read & list

The collection page shows all documents in a table. If a display name rule is set (e.g. {title}), the human-readable name appears in the first column instead of the raw ID. Click any row to open the document.

bashCLI
wren collections
wren list pages
wren list pages --limit=5 --label=published
wren get pages abc123
wren get pages abc123 --label=published
httpREST
GET /collections
GET /pages?limit=20&offset=0
GET /pages?label=published
GET /pages/abc123
GET /pages/abc123?label=published
Document editor showing JSON content

Update a document

Open a document and click the Document tab. Edit the JSON directly in the textarea and click Save. Every save creates a new immutable version — the old data is never overwritten.

bashCLI
wren update pages abc123 '{"title":"Hello World","published":true}'
httpREST
PUT /pages/abc123
{"title":"Hello World","published":true}

Delete a document

Click the red Delete button on the document page. The button requires a confirmation click. Deletion is a soft-delete — the document is hidden but its versions remain in the database.

bashCLI
wren delete pages abc123
httpREST
DELETE /pages/abc123

Natural keys and upsert-by-key

Every document has a UUID, but most content also has a human-readable identifier — a slug, a sku, a path. WREN lets you declare one field as the collection’s natural key and then interact with documents by that value instead of the UUID.

Step 1 — declare the key. Set naturalKey on the collection schema:

bashCLI
wren schema set pages \
  '{"type":"object","required":["slug","title"],...}' \
  --natural-key=slug
httpREST
PUT /api/v1/pages/_schema
{
  "naturalKey": "slug",
  "schema": {"type":"object","required":["slug","title"],...}
}

Step 2 — upsert. PUT /api/v1/pages/by-key/about creates the document if it doesn’t exist, or updates it if it does — in a single transactional call. The 20-line list-then-find-then-put loop in every push script becomes one API call:

bashCLI
# First run → 201 (created)
wren upsert pages about \
  '{"slug":"about","title":"About Us"}'

# Second run → 200 (updated, version 2)
wren upsert pages about \
  '{"slug":"about","title":"About Us (updated)"}'
httpREST
PUT /api/v1/pages/by-key/about
{"slug":"about","title":"About Us"}
→ 201 { "id": "abc", "version": 1, "naturalKey": "about", ... }

PUT /api/v1/pages/by-key/about
{"slug":"about","title":"About Us (updated)"}
→ 200 { "id": "abc", "version": 2, ... }

Step 3 — read and delete by key.

bashCLI
wren get-by-key pages about
wren delete-by-key pages about
httpREST
GET    /api/v1/pages/by-key/about
DELETE /api/v1/pages/by-key/about
How keys auto-sync. The natural key is stored as a column on the document and updated on every write. If you rename data.slug from "about" to "about-us" via a regular PUT, the column moves too — the document is now addressable at /by-key/about-us and the old key is released.
Uniqueness is enforced. Two documents in the same collection cannot share a key. WREN returns 409 Conflict if a collision is detected. Deleted documents release their key slot (they’re excluded from the unique index), so you can recreate at the same key after deletion.

Public reads also work via the org slug: GET /api/v1/orgs/{slug}/pages/by-key/about (or the short alias /orgs/{slug}/pages/by-key/about), gated by the usual principal=* read rule on the collection.


4 Binary assets

Collections can store binary files (images, videos, PDFs, etc.) instead of JSON. Set Collection type to Binary assets in the Schema tab to enable this mode.

Upload a file

  1. 1 Open a binary collection.
  2. 2 Click Upload file.
  3. 3 Choose a file and click Upload. The filename, MIME type, and size are stored automatically.
bashCLI
wren upload images ./photo.jpg
httpREST
POST /images   (multipart/form-data, field: file)
→ {"id":"xyz","version":1,"data":{
    "filename":"photo.jpg",
    "mimeType":"image/jpeg",
    "size":204800
  }}

Replace & download

Open a binary asset to see a preview (images, video, audio, and PDFs render inline). Use the Download button to save the file, or scroll to Replace file to upload a new version while keeping the history.

A Metadata card below the file controls shows the document's JSON — system fields like filename, mimeType, and size, plus any custom fields you added. You can edit and save metadata independently of the file.

Every version of a binary asset is independently downloadable via the raw URL.

bashCLI
# Download current version
wren download images xyz --out ./photo.jpg

# Download a specific version
wren download images xyz --version=2 --out ./photo-v2.jpg

# Upload a new version (replaces content, keeps history)
wren upload-version images xyz ./photo-v2.jpg
httpREST
GET /images/xyz/raw
GET /images/xyz/raw?version=2
PUT /images/xyz   (multipart/form-data, field: file)

5 Versioning

Every mutation — create, update, rollback — produces a new numbered version. Versions start at 1, increment by 1, and are immutable. Nothing is ever deleted from the version history.

View history

Open any document and click the History tab. A timeline shows every version, newest first. Click View on any entry to expand the raw data for that version.

bashCLI
wren versions pages abc123
httpREST
GET /pages/abc123/versions
GET /pages/abc123/versions/2
Version history timeline showing 3 versions

Diff versions

In the History tab, click Compare versions, pick any two version numbers from the dropdowns, then click Show diff. The response shows what changed between them.

bashCLI
wren diff pages abc123 --v1=1 --v2=3
httpREST
GET /pages/abc123/diff?v1=1&v2=3
Diff view comparing version 1 and version 3

Rollback

Rolling back doesn't erase versions — it creates a new version whose content matches the target. In the History tab, click Rollback here on any entry and confirm.

bashCLI
# Creates v4 = copy of v1's data
wren rollback pages abc123 1
httpREST
POST /pages/abc123/rollback/1

6 Labels

A label is a named pointer to a specific version — like a Git tag. Moving a label never changes the underlying versions. Common labels: published, stable, draft.

Labels tab with label name field
🏷️
Open a document → Labels tab → type a label name → optionally pick a version from the dropdown → click Set label. The label moves to that version; all other versions are untouched.
Common pattern: write new versions freely (drafts, experiments). Only move the published label when you're ready. Readers always fetch by label, so they always get the approved version regardless of how many drafts exist.
bashCLI
# Point "published" at the current version
wren label pages abc123 published

# Point "archive" at version 1 specifically
wren label pages abc123 archive --version=1

# Fetch whichever version "published" points to
wren get pages abc123 --label=published

# List documents filtered to a label
wren list pages --label=published
httpREST
POST /pages/abc123/labels
{"label":"published"}

POST /pages/abc123/labels
{"label":"archive","version":1}

GET /pages/abc123?label=published
GET /pages?label=published

7 JSON Schemas

By default a new collection accepts any JSON object. Attach a JSON Schema to enforce a strict shape — every write is validated and rejected with 422 if it fails.

Set a schema

  1. 1 Open a collection and click the Schema tab.
  2. 2 Choose Collection type: JSON documents or Binary assets.
  3. 3 For JSON collections, optionally set a Display name rule (e.g. {title}) and configure List columns — type a field name and click Add, then drag rows to reorder or click × to remove them.
  4. 4 Edit the JSON Schema in the textarea.
  5. 5 Click Save. Click Remove schema to disable validation entirely.
Start permissive. The default open schema ("additionalProperties": true) accepts any object. Tighten it once your shape is stable.
jsonExample schema
{
  "type": "object",
  "required": ["title"],
  "properties": {
    "title":     { "type": "string", "minLength": 1 },
    "published": { "type": "boolean" },
    "body":      { "type": "string" }
  },
  "additionalProperties": false
}
bashCLI
wren schema get pages
wren schema set pages '{"type":"object","required":["title"],...}'
wren schema set pages --type=binary   # binary collection
wren schema delete pages
httpREST
GET  /pages/_schema
PUT  /pages/_schema
  {"collectionType":"json","schema":{…},"displayName":"{title}"}
DELETE /pages/_schema
JSON Schema editor for the articles collection

Display name rules

A display name rule is a template that extracts a human-readable name from document data. Set it in the Display name rule field on the Schema tab, for example {title} or {first} {last}. This name is shown in the documents list instead of the raw UUID.

bashCLI
wren schema set pages \
  '{"type":"object","additionalProperties":true}' \
  --display-name="{title}"
httpREST
PUT /pages/_schema
{
  "collectionType": "json",
  "displayName": "{title}",
  "schema": {"type":"object","additionalProperties":true}
}

Changing a schema on existing data

WREN does not validate existing documents when you set or change a schema. The rules apply going forward:

  • POST a new document — validated against the current schema; rejected with 422 if it fails.
  • PUT an update — same. Every write must satisfy the current schema.
  • Existing documents — left untouched. If you tighten the schema, already-stored documents that no longer match are grandfathered: they keep working, but you cannot update them without also bringing them into compliance.
  • Rollback — writes a new version containing the old data without running validation. This means you can roll a document back to a pre-schema version even after tightening the schema. The next PUT on that document, however, must satisfy the current rules.
  • Reads — never validated. Clients always get whatever is stored.
Migration is manual. There is no "run the new schema against every existing document" tool yet. If you need every doc to satisfy a stricter shape, you have to walk the collection yourself (list all, for each one: load, transform, PUT back) — and because PUT validates, any doc you miss will fail loudly the next time something tries to update it.
Widen freely, tighten with care. Adding optional fields or removing required constraints is safe: existing docs still match. Adding new required fields, tightening types, or setting additionalProperties: false are the changes that strand existing data in "readable but not re-writable" purgatory. Test the new schema against a few real documents before shipping it.

Dry-run: validate existing docs against a schema

Before tightening a schema, you can ask WREN to run it against every existing document in the collection and report which ones would fail — without actually changing anything. Useful as a pre-flight check, or as a CI gate for schema changes.

Hit GET /api/v1/{collection}/_schema/validate to run the currently-stored schema against every document (useful for spotting grandfathered docs from a previous tightening). Or POST with a schema in the body to dry-run a proposed schema before committing to it.

bashCLI
# Validate against the currently-stored schema
wren schema validate pages

# Validate a proposed schema before saving it
wren schema validate pages '{
  "type":"object",
  "required":["title","author"],
  "properties":{
    "title":{"type":"string"},
    "author":{"type":"string"}
  }
}'

# --json for the raw response (good for piping to jq)
wren schema validate pages --json

# --max / --limit for very large collections
wren schema validate pages --max=50000 --limit=500
httpREST
# Current schema against all existing docs
GET /api/v1/pages/_schema/validate?max=10000&limit=100

# Proposed schema (wrapper form)
POST /api/v1/pages/_schema/validate
{"schema":{"type":"object","required":["title"],...}}

The response reports totals plus up to limit failing documents with their id, current version, and the exact ajv error messages:

jsonResponse
{
  "collection": "pages",
  "schemaSource": "proposed",
  "checked": 150,
  "limitReached": false,
  "valid": 147,
  "invalid": 3,
  "failures": [
    { "id": "abc", "version": 5, "errors": ["/author must be string"] },
    { "id": "def", "version": 2, "errors": ["/title must NOT have fewer than 1 characters"] },
    { "id": "ghi", "version": 1, "errors": ["/ must have required property 'author'"] }
  ],
  "failuresTruncated": false
}
Exit codes gate deployments. wren schema validate exits with status 1 when any documents would fail. So wren schema validate pages && wren schema set pages '…' && deploy.sh is a safe pipeline: the schema only gets set, and the deploy only runs, if every existing document already passes.

The endpoint is read-only. It requires read access to the collection (anyone who can read the data can dry-run a schema against it); schema changes still require admin. It caps at 50 000 documents per call — for larger collections, limitReached: true in the response tells you there's more to check.


8 Trees

A tree maps URL-style paths to documents — like a filesystem or a CMS route table. A tenant can have multiple named trees (e.g. main and staging). The same document can appear in multiple trees at different paths.

Individual trees are listed in the sidebar under Trees — click a tree name to open its file browser.

Browse a tree

The tree browser works like a filesystem. At each path level you see:

  • The document assigned to the current path (with JSON preview, version badge, and links to open, reassign, or remove it).
  • A Contents list of child paths (folders and documents) — click any child to navigate deeper.
  • A New folder button to create a new child path without assigning a document first.

A clickable breadcrumb trail at the top lets you move back up the tree. Use the optional label filter to view a specific version (e.g. only published documents).

bashCLI
wren tree list
wren tree view main
wren tree get main /site/about
httpREST
GET /tree
GET /tree/main?full=true
GET /tree/main/site/about
Tree browser at the site root with folders and the assign panel

Drill into a path to see the document assigned there, its JSON preview, and any children:

Tree browser at /tournaments/masters-2026 showing the assigned document

Assign documents to a path

If a path has no document, the assign panel opens automatically. Otherwise click Reassign. The panel has three tabs:

  • Browse — select a collection from the dropdown, then click Select next to any document. Documents render the same way as in the collection list: binary assets show filename, type, and size; JSON documents show the display name (from the collection's display name rule) and any configured list columns.
  • Create new — select a collection, write JSON in the editor, and click Create & assign to create the document and assign it in one step.
  • Direct ID — paste a document ID and click Set.

Click Unassign to clear a document from a path — the path remains as an empty folder and the document itself is not deleted.

When a folder is empty (no document assigned), a Remove folder button appears above the Contents card. Click it to delete the explicit path entry. If the folder still has descendants, it will reappear as an implicit grouping.

Add a document as a child

Use the Add document button in the Contents header to assign a document to a new child path without navigating there first. Optionally type a path segment name, then pick a document in the usual way. If you leave the segment name blank, the UI derives one from the document's filename field (for binary assets) or its display name rule (case preserved, spaces replaced with hyphens) — perfect for quickly adding an index.html or My-Article to a folder.

bashCLI
# Assign a document
wren tree set main /site/about abc123

# Remove mapping (document is kept)
wren tree remove main /site/about

# Point staging at a different doc
wren tree set staging /site/about draft456
httpREST
PUT /tree/main/site/about
{"documentId":"abc123"}

DELETE /tree/main/site/about

Paths tab (from a document)

Open any document and click the Paths tab to see every tree path that points to it. Useful for understanding where a document is referenced before moving or deleting it.

bashCLI
wren paths pages abc123
httpREST
GET /pages/abc123/paths
Paths tab showing /tournaments/masters-2026
CMS pattern: store pages in a pages collection. Map them into the main tree at their public URLs. For previews, use a staging tree that points to draft documents at the same paths.

9 API keys

API keys let scripts and services authenticate without a user session. Keys are prefixed with wren_ and passed as a Bearer token.

🔑
Settings → API KeysCreate key → give it a name → copy the key shown (it's only displayed once).
API Keys management page
Secret shown once. Copy the full key immediately after creation — it's hashed before storage and cannot be retrieved again.
bashCLI
wren keys list
wren keys create "ci-deploy"
wren keys revoke <keyId>
httpREST
GET    /api/keys
POST   /api/keys  {"name":"ci-deploy"}
DELETE /api/keys/<keyId>

# Use a key:
Authorization: Bearer wren_abc123…

Self-check: who am I?

After setting up a key, confirm it works and see what it can access with GET /api/me. The endpoint returns the calling principal, the org the request is scoped to, the caller's role, and all permission rules that apply — useful for scripts and agents that want to preflight scope before acting.

bashCLI
wren me
# or: wren auth whoami
httpREST
GET /api/me
Authorization: Bearer wren_abc123…

{
  "principal": "key:a3f4e5d2",
  "authMethod": "api_key",
  "user": { "id": "…", "name": "Jane", "email": "…" },
  "org":  { "id": "…", "slug": "coral-tide-fox", "role": "owner" },
  "apiKey": { "name": "ci-deploy", "prefix": "wren_abc1" },
  "permissions": [ … ]
}

10 Collaborators & orgs

Invite other users to your organisation. They get their own login but share access to your collections, trees, and settings.

Collaborators page showing members and invite tabs

Invite a collaborator

  1. 1 Go to Settings → Collaborators → Sent invites.
  2. 2 Enter the invitee's email address, choose a role (viewer / editor / admin), and click Send invite.
  3. 3 Share the invite link with the recipient, or they can accept via the pending invitations callout that appears at the top of their Collaborators page.
bashCLI
wren invites send [email protected] --role=editor
wren invites list
wren invites revoke <inviteId>
httpREST
POST /api/invites
{"email":"[email protected]","role":"editor"}

GET    /api/invites
DELETE /api/invites/<inviteId>

Accept an invite

When you receive an invite link (/admin#/accept/TOKEN), open it in a browser while logged in. If you're not logged in you'll be prompted to sign in first, then redirected back to complete acceptance. Alternatively, a callout appears at the top of Settings → Collaborators listing any pending invitations with an Accept button — visible regardless of which workspace you have selected in the org switcher.

bashCLI
# Accept by invite ID (from "received" list)
wren invites received
wren invites accept-by-id <inviteId>

# Accept by token (from invite URL)
wren invites accept <token>
httpREST
GET /api/invites/received
POST /api/invites/<inviteId>/accept
POST /api/invites/accept  {"token":"…"}

To remove a member: Settings → Collaborators → Members → click Remove.

bashCLI
wren members list
wren members remove <userId>
httpREST
GET    /api/members
DELETE /api/members/<userId>

11 Permissions

Permissions page with access rules

Permissions control what each principal (a member or API key) can do on a given resource (a collection, all collections, or everything). Access levels are none, read, write, and admin.

Go to Settings → Permissions to manage rules, or open a collection's Access tab to manage rules scoped to that collection.

🔒
Resource format:
collection:pages — a specific collection
collection:* — all collections
* — everything (collections, trees, settings)

Principal format:
member:<userId>, key:<keyId>, member:* (all members), * (everyone)
Optional filters: a permission can include a labelFilter (only documents with a specific label are visible) or a filterExpr (a JMESPath / JSONata expression that's applied to each document before returning it). Use these to expose only published content or strip private fields from API key responses.
bashCLI
wren permissions list

# Give an API key read access to one collection
wren permissions create \
  --principal=key:<keyId> \
  --resource=collection:pages \
  --access=read

# Give all members read/write on all collections
wren permissions create \
  --principal=member:* \
  --resource=collection:* \
  --access=write

# Only expose published documents to a key
wren permissions create \
  --principal=key:<keyId> \
  --resource=collection:pages \
  --access=read \
  --label-filter=published

wren permissions delete <permissionId>
httpREST
GET  /api/permissions
POST /api/permissions
{
  "principal": "key:<keyId>",
  "resource":  "collection:pages",
  "access":    "read",
  "labelFilter": "published"
}
PUT    /api/permissions/<id>
DELETE /api/permissions/<id>

12 CLI reference

Setup

bash
cd wren/cli
bun install
bun link           # registers "wren" globally

wren config --url http://localhost:4000
wren auth login -e [email protected] -p yourpassword
wren auth whoami

Full command list

bash
# ── Auth ──────────────────────────────────────────────────
wren auth login -e <email> -p <password>
wren auth logout
wren auth whoami

# ── Org ───────────────────────────────────────────────────
wren org current
wren org switch <orgId>

# ── Collections ───────────────────────────────────────────
wren collections

# ── Documents ─────────────────────────────────────────────
wren list <collection> [--filter] [--limit] [--label]
wren get <collection> <id> [--label]
wren create <collection> '<json>'
wren update <collection> <id> '<json>'
wren delete <collection> <id>
wren paths <collection> <id>

# ── Versions ──────────────────────────────────────────────
wren versions <collection> <id>
wren diff <collection> <id> --v1=N --v2=N
wren rollback <collection> <id> <version>
wren label <collection> <id> <label> [--version=N]

# ── Schemas ───────────────────────────────────────────────
wren schema get <collection>
wren schema set <collection> '<json-schema>' [--display-name="{field}"] [--type=binary]
wren schema delete <collection>

# ── Binary assets ─────────────────────────────────────────
wren upload <collection> <file>
wren upload-version <collection> <id> <file>
wren download <collection> <id> [--version=N] [--out=<path>]

# ── Trees ─────────────────────────────────────────────────
wren tree list
wren tree view <treeName>
wren tree get <treeName> <path>
wren tree set <treeName> <path> <documentId>
wren tree remove <treeName> <path>

# ── API keys ──────────────────────────────────────────────
wren keys list
wren keys create <name>
wren keys revoke <id>

# ── Collaborators ─────────────────────────────────────────
wren invites send <email> [--role=viewer|editor|admin]
wren invites list
wren invites received
wren invites accept-by-id <inviteId>
wren invites accept <token>
wren invites revoke <id>
wren members list
wren members remove <userId>

# ── Permissions ───────────────────────────────────────────
wren permissions list
wren permissions create --principal=<p> --resource=<r> --access=<a> \
                        [--label-filter=<label>] \
                        [--filter-lang=jmespath|jsonata] [--filter-expr=<expr>] \
                        [--audit-reads] [--audit-writes]
wren permissions update <id> [same options]
wren permissions delete <id>
Interactive API docs are at /docs — a full Scalar UI over the OpenAPI spec with live request examples for every endpoint.

13 Client libraries

WREN ships official client libraries for TypeScript/JavaScript and Python. Both cover the full API surface — documents, versions, labels, diff, schemas, trees, keys, invites, permissions — with complete type definitions.

TypeScript / Node / Bun

Works in Node.js ≥ 18, Bun, and the browser. Zero runtime dependencies — uses the native fetch API.

bash
npm install @wren/client
# or: bun add @wren/client
typescript
import { WrenClient } from "@wren/client";

const wren = new WrenClient({
  baseUrl: "https://your-wren-instance.example.com",
  apiKey: "wren_…",          // from Admin UI → API Keys
});

// Create a document
const doc = await wren.documents.create("articles", {
  title: "My first article",
  body:  "Hello, world!",
});
console.log(doc.id, doc.version); // "uuid…", 1

// Update (new version created automatically)
const v2 = await wren.documents.update("articles", doc.id, {
  title: "My first article (revised)",
  body:  "Hello, world! — edited",
});
console.log(v2.version); // 2

// View full version history
const history = await wren.versions.list("articles", doc.id);
console.log(history.versions); // [{version:1,…}, {version:2,…}]

// Diff two versions
const diff = await wren.diff.compare("articles", doc.id, 1, 2);
diff.diff.forEach(d => console.log(d.op, d.path, d.value));

// Pin a label
await wren.labels.set("articles", doc.id, "published");

// Read back via label
const live = await wren.documents.get("articles", doc.id, { label: "published" });

// Rollback to v1
await wren.versions.rollback("articles", doc.id, 1);
Error handling — the client throws typed errors: WrenNotFoundError (404), WrenUnauthorizedError (401), WrenValidationError (422 with per-field details). Import them from @wren/client and use instanceof to handle specific cases.

Python

Supports Python 3.9+. Provides both a synchronous client and an async client (via httpx). Fully typed with dataclasses.

bash
pip install wren-client
python
from wren import WrenClient

# Use as a context manager — connection is closed automatically
with WrenClient("https://your-wren-instance.example.com", api_key="wren_…") as wren:

    # Create a document
    doc = wren.documents.create("articles", {
        "title": "My first article",
        "body":  "Hello, world!",
    })
    print(doc.id, doc.version)  # "uuid…", 1

    # Update
    v2 = wren.documents.update("articles", doc.id, {
        "title": "My first article (revised)",
    })
    print(v2.version)  # 2

    # View version history
    history = wren.versions.list("articles", doc.id)
    for v in history.versions:
        print(v.version, v.labels, v.created_at)

    # Diff two versions
    result = wren.diff.compare("articles", doc.id, 1, 2)
    for entry in result.diff:
        print(entry.op, entry.path, entry.value)

    # Pin a label
    wren.labels.set("articles", doc.id, "published")

    # Read via label
    live = wren.documents.get("articles", doc.id, label="published")

    # Rollback to v1
    wren.versions.rollback("articles", doc.id, 1)
python — async
from wren import AsyncWrenClient

async def main():
    async with AsyncWrenClient("https://…", api_key="wren_…") as wren:
        doc = await wren.documents.create("articles", {"title": "Async hello"})
        history = await wren.versions.list("articles", doc.id)
        await wren.labels.set("articles", doc.id, "published")
Error handling — the Python client raises WrenNotFoundError, WrenUnauthorizedError, WrenForbiddenError, and WrenValidationError (with a .details list of field errors). All are subclasses of WrenError, which exposes .status and .body.