# claro.show for agents (HTTP API, nothing to install)

claro.show makes fun, shareable websites for your human. **You write plain semantic HTML (and, if you like, layout CSS); a world** (a 1999 homepage, a moodboard canvas, a cel-shaded 3D sea of islands…) **does all the styling** and adds interactive bits: guestbooks, RSVPs, polls, countdowns, music. You get a link to send them.

Use it when your human asks for a website, a page, an invite, a fun site for an event, a little site about them or their thing, or wants to "put something online that's not boring".

- Base URL: `https://claro.show` · JSON in, JSON out.
- **Sign in once (claro.show is invite-only right now):** ask your human for their email, `POST https://claro.show/api/v1/login {"email":"…"}`, ask them for the 6-digit code that was emailed to them (or, if you can read their email, fetch it yourself: subject "<code> is your claro.show code"), then `POST https://claro.show/api/v1/login/verify {"email":"…","code":"…","label":"<your app>"}` → `{"key":"cs_…"}`. Save the key (e.g. `~/.config/claro-show/credentials`, mode 0600) and send `Authorization: Bearer cs_…` on every call below. Reading worlds and guides doesn't need it.
- Send `X-Claro-Client: <your-app>/<detail>` on every call (e.g. `claude-code/agent`, `codex/agent`).
- Everything below works with `curl`.

## The flow
1. **Ask 2–3 quick questions** (what's it for, who's it for, the vibe) and ask for their photos. Then make something; don't interrogate.
2. **Pick a world:** `GET https://claro.show/api/v1/worlds` (includes a sample picture of each). Describe them in plain words, or pick one and say why.
3. **Write the page** as plain HTML (see "What to write" below). Read the world's details once: `GET https://claro.show/api/v1/guide?world=<id>`.
4. **Photos from the human's computer:** upload each one first, then use the returned `url` in `<img src>`. claro.show can't read local file paths.
   ```bash
   curl -sS https://claro.show/api/v1/assets -H "X-Claro-Client: my-agent/1" -H "Authorization: Bearer $CLARO_KEY" -H "Content-Type: image/jpeg" --data-binary @/path/to/photo.jpg
   # → {"url":"https://claro.show/a/3f2c….jpg"}
   ```
   Image URLs from the web work directly in `src`.
5. **Preview** (not public): renders the page and returns **screenshots (desktop + phone), so download and look at both**, plus `checks` to fix.
   ```bash
   curl -sS https://claro.show/api/v1/preview -H "X-Claro-Client: my-agent/1" -H "Authorization: Bearer $CLARO_KEY" -H "Content-Type: application/json" \
     -d "$(jq -n --rawfile html page.html '{world:"oldschool", html:$html}')"
   # → {"preview_url":"…", "screenshots":[{"name":"desktop","url":"…"},{"name":"phone","url":"…"}], "checks":[…], "read_as":"…"}
   curl -sS -o shot.jpg "<screenshots[0].url>"   # then open/view shot.jpg
   ```
6. **Show your human before publishing** (the screenshot or the preview link) and ask if it's good, unless they told you to just put it live. Change things based on what they say, then preview again.
7. **Publish** once they're happy:
   ```bash
   curl -sS https://claro.show/api/v1/pages -H "X-Claro-Client: my-agent/1" -H "Authorization: Bearer $CLARO_KEY" -H "Content-Type: application/json" \
     -d "$(jq -n --rawfile html page.html '{world:"oldschool", html:$html}')"
   # → {"url":"…", "page_id":"…", "edit_token":"…", "owner_url":"…#edit=…", "screenshots":[…], "checks":[…]}
   ```
   Share `url` exactly as returned. Give the human the **`owner_url`** to save: with it, any agent (also in a new chat) can edit the page or read RSVPs. Never put the edit token on the page.
   **Short link:** a page can live at `https://claro.show/<name>` (e.g. `https://claro.show/sams-40th`) instead of `/p/<page_id>`. Ask your human if they'd like one and what it should be, then add `"name":"sams-40th"` to the publish body, or later `PUT https://claro.show/api/v1/pages/<page_id>/name {"name":"…"}`. If it's taken you get `409 name_taken` with free `suggestions` to offer (nothing is published until the name works). Check first with `GET https://claro.show/api/v1/names/<name>`. Renaming is fine: old names keep redirecting. Lowercase letters, numbers and hyphens, 2–30 characters.
8. **Edit later:** `PUT https://claro.show/api/v1/pages/<page_id>` with the same body. Your own pages only need your key; someone else's need their edit token (`"owner_url":"…"` or `"edit_token":"…"` in the body, or header `X-Claro-Edit-Token`). The link stays the same. Switching worlds = the same HTML with another `world`.
9. **Check activity** ("who's coming?", "did anyone sign?"): `GET https://claro.show/api/v1/pages/<page_id>` with your key (your pages) or `X-Claro-Edit-Token: <edit_token>`. `GET https://claro.show/api/v1/me` lists your pages. It returns the guestbook, poll results, visitors, and (owner only) RSVP names + head counts, messages and email signups. It also returns the current `html` so you can edit it.

Don't have `jq`? Any way of sending JSON works: `{"world":"…","html":"<h1>…</h1>…"}`. Python, node, or a heredoc are all fine.

Errors look like `{"error":{"code":"…","message":"…"}}` with a hint on how to fix it.

| Endpoint | What |
|---|---|
| `POST /api/v1/login` · `POST /api/v1/login/verify` | email code → key (`cs_…`) |
| `GET /api/v1/me` · `DELETE /api/v1/me/keys/<id>` | you, your keys, your pages · revoke a key |
| `GET /api/v1/worlds` | worlds + sample pictures |
| `GET /api/v1/guide?world=<id>` | how each thing looks in that world, its options, custom code |
| `POST /api/v1/assets` | upload a photo/audio/video (raw bytes + Content-Type), or `{"url":"https://…"}` to import one |
| `POST /api/v1/preview` | `{world, html}` or `{world, page_id}` → screenshots + checks + private preview link |
| `POST /api/v1/pages` | `{world, html}` → publish |
| `PUT /api/v1/pages/<id>` | update (Bearer edit_token or owner_url) |
| `GET /api/v1/pages/<id>` | page + activity (private parts with the token) |
| `PUT /api/v1/pages/<id>/name` · `DELETE /api/v1/pages/<id>/names/<name>` | give a page its short link `https://claro.show/<name>` · free an old name |
| `GET /api/v1/names/<name>` | is `https://claro.show/<name>` free? (+ suggestions) |

| `GET /api/v1/pages/<id>/data` | the page's collections (fields, row counts) + its inputs (guestbook, RSVPs…) |
| `GET /api/v1/pages/<id>/data/<collection>` | rows (`?limit=&offset=&order=-date&state=draft&<field>=<value>`); `…/<row>` = one row |
| `POST /api/v1/pages/<id>/data/<collection>` | add a row (`{field: value, …}`) or several (`{"rows": [...]}`); optional `status: "draft"`, `publish_at`, `slug`; `"look": true` = screenshots |
| `PATCH /api/v1/pages/<id>/data/<collection>/<row>` · `DELETE …` | change some fields (optional `rev` you read, to avoid overwriting) · remove (kept in history) |
| `GET /api/v1/pages/<id>/history` · `POST …/history/<n>/restore` | recent changes and deletions · put one back |

Wherever a page id goes, its name or its link works too. Collections (blog posts, menus, scores) are explained under "Things that grow" below: declare them in the page, then add rows with `POST …/data/<collection>` without republishing (where the guide below says `write_data` / `read_data`, use these endpoints).


## What to write

Write a normal web page: headings, paragraphs, images, lists. Order matters: top to bottom is the reading order. Use `<section>` to group things that belong together (each world groups them visually). Don't style it (no colors, fonts, borders): the world does that. You *can* arrange it, see "Arrange it" below.

| You write | It becomes |
|---|---|
| `<h1>` (one, the title), `<h2>`, `<h3>` | title and section headings |
| `<p>`, `<ul>`/`<ol>`, `<em>`, `<strong>`, `<a>` | text, lists, links |
| `<img src="..." alt="...">` | a photo. `src` is a URL. With the claro.show MCP tools it can also be a **local file path** (`~/Desktop/pic.png`); over the HTTP API, upload local photos first (`POST /api/v1/assets`). Always add `alt`. |
| `<ul>` with an `<img>` in each `<li>` (text in the li = caption) | a gallery |
| `<dl><dt>Label</dt><dd>Value</dd>…</dl>` | facts / stats ("Born: 1996") |
| `<blockquote>Quote <cite>Who</cite></blockquote>` | a quote |
| `<time datetime="2026-10-25T21:00">Sat 9pm</time>` | a date; a standalone future `<time>` becomes a countdown |
| `<w-countdown to="2026-10-25T21:00">until the party</w-countdown>` | a live countdown |
| `<w-place name="Café Ruffles" address="Grove St 12, Amsterdam"></w-place>` (or `<address>`) | a location with a map link |
| A Spotify / YouTube / SoundCloud link alone in a `<p>` (or `<w-music src="…">`) | a music or video player |
| `<nav>` or a `<ul>` of only links | a row of links |
| `<details><summary>Click me</summary>…</details>` | a hidden surprise / secret |
| `<pre>` | ASCII art or code, kept exactly |
| `<w-person name="Lotte" image="photo.jpg" hair="long wavy auburn" wears="yellow raincoat, jeans">what they say</w-person>` | a person. Flat worlds show their photo in the world's style (1-bit on the Mac, chunky pixels in Old School); the 3D Sea builds them as a character from your description. **Look at your human's photos and describe people like you'd describe a picture** (hair, skin, clothes, glasses, hat, beard, what they're holding). Details: `read_guide("sea")` |
| `<w-identity name="Lotte" image="…" handle="@lotte">short bio</w-identity>` | a profile card (name, photo, handle) |
| `<w-figure kind="bar" data="Puns:90,Focaccia:100"></w-figure>` (kind: bar · stat · rating, add `max`) | stats / bars / stars |

### Things visitors can do

| You write | What happens |
|---|---|
| `<w-guestbook>Sign the guestbook!</w-guestbook>` | visitors leave name + message. **Public**: shown on the page |
| `<w-rsvp ask="Any food wishes or allergies?">Are you coming?</w-rsvp>` | visitors answer yes/no + name. **Private**: only the owner sees names (via `get_page`); the page shows a count. Optional `ask="…"` adds one question to answer (food, plus-one names, song requests); answers reach the owner with the RSVP |
| `<w-poll options="Pizza\|Tacos\|Sushi">What should we eat?</w-poll>` | vote; results shown |
| `<w-message>Send me a message</w-message>` | contact form. **Private** |
| `<w-subscribe>Get updates</w-subscribe>` | email signup. **Private** |
| `<w-react></w-react>` | emoji reactions ❤️ 😂 🔥 😭 🎉 |
| `<w-counter source="views">visitors</w-counter>` | live counter (`views`, `rsvps`, `entries`, `clock`) |

Optional: `data-prominence="3"` on any element = make it bigger/more important (1 = smaller).

## Things that grow: collections (a blog, a menu, a wall of wishes, high scores)

When the page has *a list of like things that will change* (blog posts, recipes, a menu, gigs, wishes from friends, a game's scores), make it a **collection**: a little database that belongs to the page. You declare it once; later you add, change or remove rows with `write_data` **without republishing**, and the world renders every row in its own style. Posts get their own page and an RSS feed.

```html
<head>
  <script type="claro/collections">
  {
    "posts": {
      "fields": { "title": "text", "date": "date", "cover": "image", "body": "rich" },
      "rows": [ { "title": "Hello!", "date": "2026-10-20", "body": "<p>My first post.</p>" } ]
    },
    "wishes": { "fields": { "name": "text!", "wish": "longtext!" }, "write": "visitors" }
  }
  </script>
</head>
<body>
  <h1>Noor's Kitchen</h1>
  <w-each collection="posts"></w-each>                       <!-- every post, newest first -->
  <h2>Leave a wish</h2>
  <w-form collection="wishes">Write Sam a wish</w-form>      <!-- visitors add rows -->
  <w-each collection="wishes"><p><strong>{name}</strong>: {wish}</p></w-each>
</body>
```

- **Field types:** `text`, `longtext`, `rich` (HTML: the post body), `number`, `bool`, `date`, `datetime`, `image` (a URL or uploaded photo), `url`, `email`, `tags`, or a list of options (`["Small", "Large"]`). Add `!` (`"text!"`) for required.
- **Who writes:** `"write": "owner"` (default: only you, through your agent) or `"visitors"` (a `<w-form>` on the page; they can't send rich text or pictures). **Who reads:** `"read": "public"` (default) or `"owner"` (private, like RSVPs: only you see them with `read_data`).
- **Showing rows:** `<w-each collection="posts">` with no inside shows each row the world's way. Or write how one row looks, with `{field}` placeholders: `<w-each collection="posts" limit="5" order="-date" where="tags=travel"><article><h2>{title}</h2><img src="{cover}" alt="{title}"><p>{body.short}</p></article></w-each>`. `{body}` alone in a `<p>` is the whole rich body; `{body.short}` is a 1–2 line excerpt; `{url}` is the row's own page. Add `<w-empty>Nothing yet!</w-empty>` inside for the empty state.
- **Own pages:** a collection with a `rich` field gives every row its own page (`claro.show/<page>/posts/<row>`) with links back and to the next/previous post, plus feeds at `…/posts/feed.xml` (RSS), `atom.xml`, `feed.json`. `<w-item collection="posts">…template…</w-item>` sets how that page looks (optional). Turn it off or on with `"permalinks": false/true`.
- **Rows in the page** (`"rows": [...]`) are only the starting rows: they go in the first time the collection is published. After that, use **`write_data`** (add/update/delete; `status: "draft"` for drafts only you can see, `publish_at` to schedule a post) and **`read_data`** (also lists the page's guestbook, RSVPs, etc.). Every change is kept in history and can be restored.
- Changing the collection later (new fields) is fine: republish the page. Removing a field or collection never deletes its data.
- When you change a published page, call `preview` with its `page_id` *and* the new html: the preview then shows the real rows.

## Arrange it: your layout, the world's look

Two pages in the same world shouldn't look like the same template. Write normal CSS in a `<style>` (or `style=""`) to **arrange** things, and it's kept:

- **Kept (layout):** `display:grid|flex`, `grid-template-*`, `grid-column`/`span`, `gap`, `order`, `columns`, `width`/`max-width`/`height`/`aspect-ratio`, `margin`/`padding`, `align-*`/`justify-*`, `text-align`, `font-size`, `line-height`, `transform: rotate(…)`, `position: sticky`.
- **Routed to the world (look):** colors, fonts, borders, shadows, radii, gradients. They're ignored, except **a `background` color becomes a tone** of that element in the world's own palette. Want a different font or color for the whole page? Use the treatment knobs below.
- **Phones:** the world stacks everything into one tidy column on phones. Add your own `@media (max-width: 600px) { … }` if you want something else there.

**Build your own components out of the building blocks.** A menu card, a tracklist, a "now/next" strip, a tier list, a 2×2 grid of facts, a magazine cover: put the pieces in a `<div>`/`<section>` and lay them out with CSS. They keep working (guestbooks, RSVPs, text version) and keep the world's style, so when someone switches the font or accent, your component updates too.

```html
<style>
  .cover { display: grid; grid-template-columns: 1.4fr 1fr; gap: 20px; align-items: end }
  .cover h1 { font-size: 110px; line-height: .9; grid-column: 1 / -1 }
  .menu { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 40px; text-align: center }
  .stamp { transform: rotate(-4deg); background: tomato }   /* rotation kept, background → a tone */
</style>
<section class="cover"><h1>Summer of Bread</h1><img src="…" alt="…"><p class="stamp">Issue 03</p></section>
<section class="menu"><div><h2>Antipasti</h2><ul><li>Burrata</li></ul></div><div><h2>Dolci</h2><ul><li>Tiramisu</li></ul></div></section>
```

Not allowed: hiding content (`display:none`, `visibility`), `position: fixed/absolute`, images in CSS (`url()`). In spatial worlds use `data-pos="x,y"` (Canvas) or `data-pos` on a section / `data-object` (Sea) to place things instead.

## Custom stuff: `<w-program>`

For anything the building blocks don't cover (a mini game, a toy, a drawing, a custom widget), write your own HTML/CSS/JS inside `<w-program>`. It runs sandboxed inside the world.

```html
<w-program height="320">
  <style> button { font-family: var(--w-display); background: var(--w-accent); color: var(--w-paper); } </style>
  <button id="b">Feed the cat</button> <p id="n">0 snacks</p>
  <script> let k = 0; b.onclick = () => n.textContent = ++k + " snacks"; </script>
  <p slot="fallback">A button to feed Bram the cat.</p>
</w-program>
```

- **Look like the world:** use the world's CSS variables, `var(--w-ink)`, `--w-paper`, `--w-accent`, `--w-link`, `--w-display` (heading font), `--w-body`, `--w-mono`, `--w-bevel`, `--w-radius`. Then your thing fits in every world automatically. Each world guide lists its values.
- Always include a `slot="fallback"` child (one line or an image): it's shown in link previews and the text version.
- `height` = starting height in px (it grows to fit). Scripts can't reach the parent page, cookies or the network.
- **Data:** a program can read and add rows of the page's collections: `await claro.data("scores").list({ limit: 10 })` (public collections) and `await claro.data("scores").add({ name: "Ana", score: 42 })` (collections with `"write": "visitors"`). That's how you make a high-score table or a shared counter. Normal `<form>`s work inside a program (handle `submit` and call `preventDefault()`):
  ```html
  <w-program height="300"><form id="f"><input name="name" required> <input name="score" type="number" required> <button>Save</button></form><ol id="list"></ol>
  <script>
    const show = async () => list.innerHTML = (await claro.data("scores").list({ limit: 10 })).map((r) => `<li>${r.name}: ${r.score}</li>`).join("");
    f.onsubmit = async (e) => { e.preventDefault(); await claro.data("scores").add({ name: f.name.value, score: Number(f.score.value) }); f.reset(); show(); };
    show();
  </script><p slot="fallback">The high scores</p></w-program>
  ```
  `list()` gives rows newest first unless the collection's `order` says otherwise (`list({ order: "-score" })` works too); `add()` throws an Error with a readable message when a value doesn't fit the field (show it to the visitor).
- Reach for `<w-program>` for *behavior* (games, toys), not for layout or looks: arrange with CSS, restyle with the treatment knobs.
- **3D (sea world only):** `<w-program kind="3d">` builds your own object on an island with a tiny API. See `read_guide("sea")`.

## Make it look like *them*: options & treatment

`<meta name="claro:OPTION" content="VALUE">` in `<head>`. Every world understands the same treatment knobs: `claro:accent`, `claro:ink`, `claro:paper`, `claro:ground`, `claro:font` (a mood like `handwritten`/`serif`/`loud`, answered by each world with its own typeface: **type is part of a world**, so you choose from its set), `claro:corners`, `claro:borders`, `claro:shadow`, `claro:texture` (photos), and `data-tone` per element (a color *request*: each world renders it in its own material). Each world also has its own options: Old School `background`/`title`/`width`/`extras`, Canvas `ground`/`layout`, Sea `time`/`sea` plus islands (`<section>`), `data-object` and `<w-prop>` set dressing. Each world guide lists them. **Never wrap normal content in `<w-program>` just to restyle it.**

## Good to know

- Every world signs its pages with a small Claro logo in its own style (a pixel sticker, a card, its own little island at sea) linking to claro.show. You don't add it.

- Keep it personal and short: 5–10 things on a page is plenty. The world adds the fun; you add the meaning.
- Don't fake photos of real people. Use the human's own photos, or none (worlds look good with just words).
- `preview` returns screenshots: **look at them** before showing the human. `checks` tell you what to fix.


## World details
- `sea`: Sea. A sunny cel-shaded ocean like Zelda: The Wind Waker. Every section is an island and every piece of content is a thing on it: signposts, a red postbox, photo easels, a treasure chest, people. Visitors start in a little boat, tap an island to sail to it, and tap things to open them. → `GET https://claro.show/api/v1/guide?world=sea`
- `canvas`: Canvas. An infinite canvas of cards you drag around. Clean, airy, a bit like a moodboard or a desk covered in notes. → `GET https://claro.show/api/v1/guide?world=canvas`
- `oldschool`: Old School. A 1999 personal homepage: tiled backgrounds, WordArt, bevels, a hit counter, a guestbook, a webring. → `GET https://claro.show/api/v1/guide?world=oldschool`
- `macos`: Classic Mac. A 1991 Macintosh desktop: every section is a draggable window with a pinstriped title bar, links are desktop icons, Chicago font, 1-bit charm. → `GET https://claro.show/api/v1/guide?world=macos`
