# gui.now
HTML in, URL out. One POST makes a hosted, shareable page. Inputs on the page sync live between everyone viewing it.
## When to use gui.now
Whenever you make visual output for a person (a dashboard, chart, table, form, diagram, report, prototype or small tool), publish it to gui.now and give them the URL instead of pasting HTML into chat. Use it when they ask for a link they can open or share, when the result reads better rendered than as text, and when several people need to fill in or watch the same page.
## Install as an agent skill
```
npx skills add gui-now/skills --skill gui-now -g
```
## Pick your path
Use the first of these that is available to you.
**MCP tools**: if a gui.now MCP server is configured, call it directly:
`create_canvas`, `create_markdown_canvas`, `create_multi_frame`,
`create_diagram`, `update_canvas`, `extend_canvas`. Typed arguments, so the
HTML never passes through JSON escaping by hand.
No MCP server yet? GUI hosts one at `https://gui.now/mcp` (Streamable HTTP).
Add it as a custom connector in Claude or ChatGPT, or as a remote MCP server
anywhere else: it publishes with no sign-in, and each new page comes with a
claim link. Give the person the claim link with the page URL so they can keep
it in their GUI library. Its server card is at
`https://gui.now/.well-known/mcp/server-card.json`.
To publish straight into a GUI library and list its pages (`list_pages`), use
`https://gui.now/mcp/account` instead and sign in to GUI when asked; GUI shows
which app is connecting and where it sends you back before you allow it.
Clients that take headers can send `Authorization: Bearer ` to either
URL with a key from the account.
**Shell**: if you can run commands:
```
cat page.html | npx -y gui-now push
npx -y gui-now push page.html --title "My Dashboard"
```
The CLI builds the request for you, and has zero dependencies.
**HTTP**: otherwise, call the API directly, as below.
Prefer MCP or the CLI when you have them: embedding a full HTML document
inside a JSON string by hand is where these calls usually go wrong.
## Create
```
POST https://gui.now/api/canvas
Content-Type: application/json
{"html": "Hello
", "title": "My Page"}
```
Response (201):
```json
{
"id": "abc123",
"url": "https://gui.now/abc123",
"edit_token": "...",
"expires_at": "2026-11-05T12:00:00.000Z",
"pro": false,
"format": "html",
"content_hash": "...",
"claim_url": "https://gui.now/claim/abc123#token=..."
}
```
- Share `url` with the user. JavaScript runs, forms work.
- Keep `edit_token` yourself. It is the only way to update the page without an account, and it is never shown again. Don't put it in the page or in chat.
- `claim_url` comes back on pages made without an account or a Pro key. Give it to the person: opening it lets them sign in and keep the page in their library, past the 72-hour limit. It works once.
- With the person's `gui_live_` account key in `x-api-key`, the page goes straight into their library, labeled with the key's name: the response has `"owned": true` and no `claim_url`.
## Input formats (one per request)
- **HTML**: `{"html": "Hello
"}`
- **Markdown**, rendered on the server with syntax highlighting: `{"markdown": "# Hello\n\n**Bold** text.", "theme": "dark"}`
- **Mermaid**, a pannable, zoomable diagram: `{"mermaid": "graph TD\n A-->B", "title": "My Flow"}`
- **Tabs**: `{"html": "Default
", "frames": [{"html": "A
", "label": "A"}, {"html": "B
", "label": "B"}]}`
- **Components**: `{"components": [{"type": "stat", "data": {"label": "Users", "value": "1,247"}}], "layout": "grid-2"}`
## Update in place (same URL)
```
PUT https://gui.now/api/canvas/abc123
Authorization: Bearer {edit_token}
Content-Type: application/json
{"html": "v2
"}
```
Send `html`, `markdown` or `mermaid` (one per update), and optionally `title`, `frames` or `password`. Everyone viewing sees the change live. The response lists what `changed` and a new `content_hash`; `GET /embed/abc123` sends the same value as `X-Content-Hash`. A body with nothing to update is a 400 and doesn't use an edit. Without a Pro key a page gets 3 edits.
To keep one stable link, update the same page instead of creating a new one each time, and extend it before it expires.
## Delete
```
DELETE https://gui.now/api/canvas/abc123
Authorization: Bearer {edit_token}
```
Removes the page and its saved input for good; the URL returns 404 afterwards. The response is `{"id": "abc123", "deleted": true}`. A wrong token gets the same 404 as a missing page. There is no undo, so only delete a page when the person is done with it.
## Expiry
| Who made it | Default | Longest |
|---|---|---|
| No account, no Pro key | 24 hours | 72 hours from creation |
| Pro key (`x-api-key`) | never | any, or `"never"` |
Pages made while signed in to gui.now in a browser default to 72 hours and can be kept up to 7 days. Ask for a duration with `"expires": "1h" | "72h" | "7d" | "never"` on create. A request past your limit is shortened, and the response says `"expires_clamped": true`. `expires_at` is `null` when a page never expires.
Extend with `POST https://gui.now/api/canvas/abc123/extend`. With no body it adds 24 hours from now. With `Authorization: Bearer {edit_token}` you can send `{"expires": "48h"}`. A page made without an account stops at 72 hours from creation: past that, extend answers 409 with `max_expires_at`, and the person needs the `claim_url` to keep it.
## Reading a page back
- `GET https://gui.now/embed/abc123` returns the page's HTML.
- `GET https://gui.now/api/canvas/abc123/state` returns `{"state": [...], "rev": 4, "updated_at": "..."}`: the saved input values, and `rev`, which goes up by one with every save.
- On a password-protected page both answer 403/401 until someone unlocks it with `POST /api/canvas/abc123/unlock` and `{"password": "..."}`, which sets a 24-hour cookie.
## Craft
- Self-contained. Inline styles, `