How-to
Take an org live
From an empty account to a site on your own domain, with data, scripts and AI agents writing safely, and a way back if something goes wrong. Each step links to the detailed guide.
1. Org and slug
Every account is an org. Sign up in the Admin UI, then give the org a readable slug before you share any URL. Public URLs and custom domains contain the slug, and changing it later breaks them.
PUT /api/v1/org/slug
{"slug": "tkd-tracker"} # 3–40 chars: lowercase letters, digits, hyphens2. People and keys
- People: invite collaborators with
POST /api/v1/invites {"email": "…", "role": "member"}(or"admin"). - One API key per script, CI job and agent (Admin UI → API Keys, or
wren keys create). Store keys as secrets, never in code or page source. A key always acts in the org it was created in. - Least privilege: keys created by the org owner bypass all permission rules. For a restricted writer, create the key from a member account and give it
key:<keyId>rules, for example write access to one collection only. - CLI:
export WREN_URL=… WREN_API_KEY=wren_…. Keys don't expire mid-run the way login sessions do.
3. Data model
As an admin, once per collection: declare a JSON Schema and a natural key, so every writer can upsert with PUT …/by-key/{key} and re-runs never create duplicates. Use ISO dates (2026-10-04) so where filters work.
PUT /api/v1/events/_schema
{"naturalKey": "slug", "schema": {"type": "object", "required": ["slug", "name", "date"]}}More: Update data and files without duplicates. For numbers you recompute after every write (standings, totals), add a materialized query.
4. Decide what's public
Nothing is public until you add a principal: "*" read rule. Decide per tree and collection:
| What | Public rule | Why |
|---|---|---|
| The site (tree) | {"resource":"tree:mysite","labelFilter":"published"} | Visitors see released pages only; deploys stay private until promote |
| Live data (scores, feeds) | {"resource":"collection:results"}, no label filter | Visible the moment it's written |
| Curated data (articles) | {"resource":"collection:articles","labelFilter":"published"} | Released together with the site |
| Personal or internal data | none | Stays private; read it with a key |
Public means discoverable. Everything a * rule allows is listed at /api/v1/projects and in /orgs/{slug}/llms.txt, including sample documents. Read both before launch.
5. First release
wren deploy ./dist --tree mysite --label preview --public # creates the published-only rule curl -H "Authorization: Bearer $WREN_API_KEY" "$WREN_URL/api/v1/tree/mysite/index.html?label=preview" wren promote mysite --from preview --label release-1 # name the release, for rollback wren promote mysite --from preview # go live, in one transaction
More: Publish a site and keep drafts private and Deploy, preview, promote.
6. Your domain
A small Cloudflare Worker maps your domain onto the public tree. It needs no API key, and it also gives the domain its own MCP endpoint:
UPSTREAM = "https://wren.aemwip.com/orgs/tkd-tracker/tree/mysite" MCP = "https://wren.aemwip.com/orgs/tkd-tracker/mcp?tree=mysite"
7. AI agents
- Your own agents connect to
https://<wren-host>/mcpwith their own key. They write topreviewby default, and you promote. - Visitors' agents use
https://your-domain/mcp: public, read-only, and limited to the site that domain shows (?tree=). Other trees and collections stay invisible. A key from another org is refused. - Give agents that only answer questions
/mcp?readonly=1.
More: Connect AI agents with MCP.
8. Edge settings (Cloudflare in front of WREN)
- Scripts get
403 error code: 1010? Turn off Browser Integrity Check for the WREN hostname (Configuration Rule), and add a WAF skip rule if Bot Fight Mode is on. See Use WREN from Python. - Caching: WREN sends
max-age=60on public reads andno-storeon errors and on everything authenticated. Make sure no Cache Rule or Browser Cache TTL overrides that with hours. - HTTPS only: share
https://URLs; plainhttp://redirects.
9. Email, backups and upgrades (self-hosted)
- Email: set
SMTP_URLandMAIL_FROMso sign-ups get a confirmation email, password resets work, and invites are delivered. Without them nothing is sent: links are written to the server log, and invite responses include the link to share yourself. SetBETTER_AUTH_URLto your publichttps://address so the links point there. Once delivery works, you can require confirmation withREQUIRE_EMAIL_VERIFICATION=true. - Back up Postgres daily with
pg_dump -Fc. Every org lives in its own schema in that one database, and file contents are stored in it too, so one dump is the whole instance. Keep a week of daily dumps plus monthly ones, and keep a copy off the machine. - Prove the backup restores: restore into a throwaway Postgres container regularly and compare table counts. A 2.4 GB dump restores in about 4–5 minutes on a desktop machine.
- Upgrades: pin the image tag, take a backup, then pull and restart. Database migrations run automatically at startup (look for
applying vNin the log). Check/healthafterwards.
Launch checklist
- ☐ Slug set and final
- ☐ One key per script, CI job and agent, stored as secrets; no keys in page source or git
- ☐ Schemas and natural keys declared for collections that scripts write
- ☐ Public rules reviewed: site with
labelFilter: "published"; live data deliberately without; nothing personal public - ☐
/orgs/{slug}/llms.txtand/api/v1/projectsread through: nothing there you don't want public - ☐ First release promoted, and a named release label exists for rollback
- ☐ Domain serves
/, deep links and assets; a missing page returns 404 - ☐
https://your-domain/mcpwithout a key shows only this site - ☐ Edge: no 1010 for your scripts; no long cache overrides
- ☐ Email delivery configured (
SMTP_URL,MAIL_FROM,BETTER_AUTH_URL); a test sign-up receives its confirmation - ☐ Backups scheduled, one restore tested
After launch
- Roll back:
wren promote mysite --from release-1. It's one transaction, and nothing is lost. - Rotate a key: create the new one, switch the script over, then revoke the old one (
wren keys revoke). - See what changed: every write is a version, so
…/versionsand…/diffshow what a script or an agent did.