# merm.sh — complete spec

> **This page is everything you need.** You do not need to fetch other docs. merm.sh hosts versioned Mermaid diagrams: one GET or POST creates a diagram, you get back a stable rendered-page URL, and that URL is what you hand to the user.

---

## ⚠ Claude.ai on the web — STOP, read this first

If you are Claude running inside Claude.ai with the default web tools: **your `web_fetch` is exact-URL-match and will refuse every URL you construct on merm.sh.** Do not attempt to fetch, curl, or "go to" merm.sh. Every attempt will fail with "domain not in allowlist" or an exact-URL permission block, and you will waste the user's turn.

What to do instead — this is the entire flow:

```text
1. Write the Mermaid source for the diagram.
2. URL-encode it (encodeURIComponent / urllib.parse.quote with safe='').
3. Emit exactly one MARKDOWN HYPERLINK in your reply. The literal form is:
     [View diagram](https://merm.sh/go/<url-encoded-mermaid>)
   — not a bare URL, not inside a code block, not "here is the URL:".
   It must render as a clickable link in chat so the user can open it
   with one click. Agents that dump the URL as plain text force the
   user to copy-paste it into their browser — that is a failure.
4. Stop. Do not fetch. Do not verify. Do not paste raw Mermaid in chat.
```

When the user clicks, their browser opens `/go/`, merm.sh creates the diagram, and redirects the browser to `https://merm.sh/d/<id>` — the rendered share URL. You never need to read a response. **The link is the API call.**

**Tell the user the above if they seem confused** — e.g. "I can't reach merm.sh from my sandbox, so I'm giving you a link; clicking it creates and opens the diagram."

If the user wants you to read diagrams back or iterate, ask them to add `merm.sh/*` to their allowed domains in Claude.ai settings (wildcard format accepted), then you can use the fetch-and-read endpoints below.

---

## Universal rule — read this even if you skip everything else

If you do **not** have unrestricted outbound HTTP (POST, arbitrary domains, no exact-URL-match check) then your only correct output is a **single markdown hyperlink** to `https://merm.sh/go/<url-encoded-mermaid>`. No exceptions.

**Do not** — regardless of which harness you are:

- ...dump the raw Mermaid source in a code fence ("```mermaid ... ```"). The user asked for a hosted diagram. Pasting code is the failure mode.
- ...render the diagram inline using your harness's built-in flowchart tool, artifact, canvas, or mermaid renderer. An inline render is ephemeral and un-shareable. The user asked for merm.sh specifically so they get a URL they can share. **Use merm.sh.**
- ...paste the URL as plain text ("here's the link: https://merm.sh/go/...") or wrap it in a code block. It must render as a clickable markdown hyperlink so the user can one-click.
- ...narrate what you did ("I mapped the article into…", "Here's the link...", "It uses the /go/ pattern...", "Clicking will create the diagram..."). The user does not need merm.sh's mechanics explained. Spend your thinking budget on diagram quality instead.

If you **do** have full HTTP (Claude Code, Codex, a real CLI, your own backend): prefer `POST https://merm.sh/api/d`. You can send arbitrarily large diagrams, you read the JSON response, you return the `url` field as a markdown hyperlink. See the CLI section below.

---

## Pick your agent, copy the one-line recipe

Real agent traces informed these. If you recognize yourself, skip the rest of the page — this is all you need.

### You are ChatGPT (browse / web.open tool)

You have two valid paths. **Prefer path 1** (hand-off link) unless the user explicitly wants you to do the full round-trip yourself.

**Path 1 — hand-off link (preferred, one-shot, zero fetches):**

Exactly these four steps. Nothing else.

1. Write the Mermaid source for the diagram.
2. URL-encode it (`urllib.parse.quote(source, safe='')` or `encodeURIComponent`).
3. Emit one markdown hyperlink in your reply:
   `[View diagram](https://merm.sh/go/<encoded>)`
4. Stop.

**ChatGPT-specific anti-patterns — observed in real traces, do NOT do these:**

- **Do not web-search for "merm.sh docs" / "merm.sh API."** The user told you the domain. You already know the recipe — it's this section. Searching is pure latency.
- **Do not fetch `merm.sh` or any sub-path to "confirm"** the recipe. You are not verifying, you are emitting. Every fetch you attempt on a constructed URL will hit "URL not seen" or "URL too dynamic" and waste a turn.
- **Do not call `open` / `web.open` on your `/go/` URL.** The URL is for the user to click in their browser. You are not the one opening it. Opening it from your sandbox is the failure mode, not the goal.
- **Do not run Python to "check URL length"** unless your encoded mermaid is clearly enormous (>6KB). For a normal article diagram, just emit the link. If it's too long, the 302 will fail for the user and you can retry with a smaller diagram — but trial-by-computing-length is always slower than just emitting and moving on.
- **Do not paste the URL as plain text or in a code block.** It must be a real markdown hyperlink so the user can one-click it.
- **Do not also paste the raw Mermaid source.** The user asked for a diagram, not code. The link IS the diagram.

If you catch yourself about to search, fetch, or compute — stop. The recipe is four steps. You are on step 3.

**Output format — this is the entire reply to the user:**

```markdown
[View diagram](https://merm.sh/go/<encoded>)

<one sentence, optional, describing what the diagram shows>
```

Nothing else. No "I mapped the article into…", no "Here's the link.", no "It uses the /go/ pattern.", no "Opening it will redirect to the rendered page.". The user does not need process narration. Spend your thinking budget on diagram quality — better nodes, cleaner edges, clearer labels.

**Path 2 — fetch-and-read (only if you need the `/d/<id>` URL in your response text):**

```text
GET https://merm.sh/?content=<url-encoded-mermaid>
```

- **Only use the homepage URL** (`https://merm.sh/?content=…`). Sub-paths like `/c/…` or `/api/d?…` trip your "URL not seen in conversation before" rule.
- **Keep the raw Mermaid under ~1KB** (encoded URL under ~3KB). ChatGPT's safety classifier rejects long constructed URLs. If yours gets rejected, shorten node labels and retry — don't search or probe.
- **Do not search for anything.** The user said "merm.sh", the domain is whitelisted, and this page has the full spec.
- **Return the `url` field from the JSON response as a markdown hyperlink** — `[View diagram](https://merm.sh/d/abc123)`. Not the URL you called — that was the write endpoint. And never a bare URL.
- **Do not fall back to raw Mermaid in chat.** If the GET fails, fall back to path 1 (hand-off link).

### You are Claude.ai on the web

**You cannot fetch constructed URLs.** Your `web_fetch` tool is exact-URL-match — a URL you built is never in its allowlist, so any attempt to call merm.sh will be blocked. Do not fetch. Instead:

```text
Emit as a clickable link in your reply:
https://merm.sh/go/<url-encoded-mermaid>
```

The user clicks the link. Their browser opens `/go/`, merm.sh creates the diagram, and their browser is **302-redirected to the rendered `/d/<id>` share URL**. You never see a response — you don't need to. The link is the API call.

- **Do not try to fetch** `/go/…`, `/?content=…`, `/c/…`, or any other merm.sh endpoint. Every attempt will fail and waste the user's turn.
- **Do not emit raw Mermaid in chat.** The user asked for a diagram link, not code. Emit the `/go/` URL and stop.
- **Keep the URL reasonable length** — encoded Mermaid under ~6KB is safest. Shrink node labels if the diagram is huge.
- **If `web_fetch` is whitelisted for all of `merm.sh`** (see "Ask the user to widen permissions" below), you can also use `https://merm.sh/?content=…` and read the response — but `/go/` always works, so prefer it.

**Ask the user to widen permissions (one-time, optional):**
If the user wants you to read the diagram back, list versions, or do anything round-trippy, have them add `merm.sh` to their allowed domains:

> Open the sidebar → **Settings** → **Connectors / Web search** (name varies by client) → add `merm.sh` as an allowed domain. Entries accept wildcard form: `*.merm.sh` covers subdomains; `merm.sh/*` covers all paths. Save.

After that, you can fetch constructed URLs on merm.sh normally. Until then, stick with `/go/`.

### You are Perplexity (or any search-answer engine with a built-in flowchart/diagram renderer)

**Do not render the Mermaid inline with your own flowchart tool.** The user said "merm.sh" — they want a shareable hosted URL, not a screenshot embedded in an answer card. An inline render is ephemeral; a merm.sh URL is persistent, versioned, and shareable.

Same recipe as ChatGPT Path 1:

1. Write the Mermaid source.
2. URL-encode it.
3. Emit one markdown hyperlink: `[View diagram](https://merm.sh/go/<encoded>)`.
4. Stop.

Do not also paste the raw Mermaid source as a code block. Do not also render it inline. One clickable link.

### You are Claude Code, Codex, or any CLI with real HTTP

```bash
curl -X POST https://merm.sh/api/d -H 'Content-Type: text/plain' -d '<your mermaid>'
```

POST has no URL length cap. Save the `secret` from the response for future updates.

### You are MCP-connected

Add `https://merm.sh/mcp` to your MCP servers. Tools: `create_diagram`, `update_diagram`, `get_diagram`, `create_board`, `add_diagram_to_board`, `get_board`.

---

## If you only read one section

Pick whichever shape your environment supports. **All three create a diagram in one call** and return the same response. `?format=json` is optional but recommended.

```bash
# Option A — sandboxed-browse agents (ChatGPT, Claude.ai web).
# Just add ?content= to the homepage URL. Works when your fetch tool
# only allows the exact domain the user mentioned and rejects
# "dynamic" sub-paths.
curl 'https://merm.sh/?content=graph%20TD%3B%20A--%3EB&format=json'

# Option B — agents that can fetch arbitrary paths on the domain.
curl 'https://merm.sh/c/graph%20TD%3B%20A--%3EB?format=json'

# Option C — anyone that can POST.
curl -X POST 'https://merm.sh/api/d' \
  -H 'Content-Type: text/plain' \
  -d 'graph TD; A-->B'
```

You get back (JSON):

```json
{
  "id": "abc123",
  "editId": "xyz789",
  "url":     "https://merm.sh/d/abc123",
  "editUrl": "https://merm.sh/be/xyz789",
  "secret":  "s3cr3t…",
  "version": 1,
  "instructions": { "share_with_user": "https://merm.sh/d/abc123", ... }
}
```

Then you tell the user: *"Your diagram is at https://merm.sh/d/abc123"*. The URL you *called* (`/?content=…`, `/c/…`, or `POST /api/d`) is the **write endpoint**, not a share link. The `url` field in the response is the output — a stable rendered-diagram page at `/d/<id>`. That is the one humans open.

### Which shape should I use?

- **Sandboxed in a browse tool** (ChatGPT browse, Claude.ai web, Perplexity): use **Option A** — `https://merm.sh/?content=…`. It only needs permission for the exact domain the user named. No path heuristic to trip on.
- **Have full HTTP access** (Claude Code, Codex, your own backend, curl in a real terminal): use **Option C** (POST) for diagrams over ~5KB, otherwise any of A/B/C works. POST avoids URL length caps and is what the CLI uses.
- **Have GET-only access but can fetch any path**: use **Option B** — `/c/<mermaid>` is slightly more idiomatic.

---

## Three URL shapes, three jobs

| Shape | Job | Who uses it |
|---|---|---|
| `https://merm.sh/d/<id>` | **Share URL.** Rendered diagram page. | Humans, in a browser. |
| `https://merm.sh/c/<url-encoded-mermaid>` | **Create.** Each call makes a new diagram. | Agents, one GET. |
| `https://merm.sh/u/<editId>/<url-encoded-mermaid>` | **Update.** Adds a version to an existing diagram. | Agents, one GET. |

Nothing is overwritten. Every update creates a new version. Prior versions stay at `https://merm.sh/api/d/<id>?v=N`.

---

## All endpoints

| Action | Method | Endpoint | Auth |
|---|---|---|---|
| Create + redirect to /d/ | GET | `https://merm.sh/go/<url-encoded-mermaid>` | none |
| Create (homepage shortcut) | GET | `https://merm.sh/?content=<url-encoded-mermaid>` | none |
| Create (URL-only path) | GET | `https://merm.sh/c/<url-encoded-mermaid>` | none |
| Create (query-style API) | GET | `https://merm.sh/api/d?content=<url-encoded-mermaid>` | none |
| Create (REST) | POST | `https://merm.sh/api/d` | none |
| Update (URL-only) | GET | `https://merm.sh/u/<editId>/<url-encoded-mermaid>` | editId in path |
| Update (REST) | PUT | `https://merm.sh/api/d/:id` | `Bearer <secret>` |
| Read diagram | GET | `https://merm.sh/api/d/:id` | none |
| Read a version | GET | `https://merm.sh/api/d/:id?v=N` | none |
| View rendered | GET | `https://merm.sh/d/:id` | none |

All create/update endpoints return the same response shape (JSON or plain text). Add `?format=json` for JSON; otherwise you get plain text.

---

## Request / response, verbatim

### GET /c/&lt;mermaid&gt;?format=json   (recommended for agents)

```bash
curl 'https://merm.sh/c/graph%20TD%3B%20A--%3EB?format=json'
```

Returns `201 Created`:

```json
{
  "id": "abc123",
  "editId": "xyz789",
  "url": "https://merm.sh/d/abc123",
  "editUrl": "https://merm.sh/be/xyz789",
  "secret": "s3cr3t…",
  "version": 1,
  "instructions": {
    "summary": "Diagram created. The \`url\` field is the shareable rendered-diagram URL — that is what you should hand back to the user.",
    "share_with_user": "https://merm.sh/d/abc123",
    "update_url_only": "https://merm.sh/u/xyz789/<url-encoded-new-mermaid>",
    "update_rest":     "PUT https://merm.sh/api/d/abc123 with Authorization: Bearer <secret>",
    "mental_model": {
      "/d/<id>":               "public share URL — humans open this",
      "/c/<mermaid>":          "create endpoint — each call makes a new diagram",
      "/u/<editId>/<mermaid>": "update endpoint — adds a version to one existing diagram"
    }
  }
}
```

### GET /c/&lt;mermaid&gt;   (plain text, default)

Same endpoint without `?format=json`. Returns the same information as a labeled plain-text block headlined by "SHARE THIS URL WITH YOUR USER". Every key field (`url`, `editUrl`, `editId`, `secret`, `version`) is also mirrored in response headers `x-diagram-url`, `x-edit-url`, `x-edit-id`, `x-diagram-secret`.

### POST /api/d   (REST, if you can send headers + body)

```bash
curl -X POST https://merm.sh/api/d \
  -H "Content-Type: application/json" \
  -d '{"content": "graph TD; A-->B", "title": "My Diagram"}'
```

Same response shape as above.

### GET /u/&lt;editId&gt;/&lt;mermaid&gt;   (URL-only update)

Adds a new version to the diagram identified by `editId`. The `id` and `url` in the response are the *same* as on create (the diagram never moves); `version` increments.

```bash
curl 'https://merm.sh/u/xyz789/graph%20TD%3B%20A--%3EB%3B%20B--%3EC'
```

### GET /api/d/:id   (read)

```bash
curl https://merm.sh/api/d/abc123
```

Returns `{ id, title, version, content, createdAt, versions: [{version, content, createdAt}...], skill }`. Append `?v=N` to read a specific version.

---

## Encoding rule (one sentence)

URL-encode your Mermaid with *nothing* left raw: percent-encode spaces, newlines, semicolons, slashes, colons, brackets. Python: `urllib.parse.quote(mermaid, safe='')`. JavaScript: `encodeURIComponent(mermaid)`.

---

## Practical limits & edge cases

- **URL length cap**: ~8KB URL (~5KB raw Mermaid after encoding). Beyond that, switch to `POST /api/d`. URLs between ~8KB and ~25KB may 502 at the edge; 25KB+ returns 414.
- **Invalid Mermaid** → `400 invalid_syntax` with the parser message. Your content is *not* saved.
- **Validator crash on our side** (rare) → the diagram is saved anyway; response includes `x-validation: skipped` and a `warning` field. Open the view URL to confirm it rendered.
- **Unicode** (Chinese, emoji, etc.) round-trips fine as long as you URL-encode it.
- **Supported types**: flowchart, sequence, class, state, entity-relationship, gantt, pie, quadrant, requirement, gitgraph, mindmap, timeline, sankey, block, packet, kanban, architecture.

---

## Agent failure modes to avoid

1. **Returning the `/c/…` URL to the user.** That's the endpoint, not the share link. Return the `url` field from the response.
2. **Not fetching the endpoint at all.** Constructing a `/c/...` URL without actually calling it does *not* create a diagram. The diagram is created *by the call*.
3. **Losing the `secret`.** It's returned exactly once on create. If you may update via REST later, store it for the session.
4. **Calling `/c/` again to update.** That creates a *new* diagram. Use `/u/<editId>/…` to add a version to an existing one.

---

## Content negotiation

Send `Accept: text/markdown` (or no `Accept` header, or anything that isn't `text/html`) to any page URL and you get clean Markdown. That's how this page is served to you. Human browsers get the rendered site.

---

## MCP (for MCP-connected agents)

```json
{ "mcpServers": { "mermaid-viewer": { "type": "http", "url": "https://merm.sh/mcp" } } }
```

Tools: `create_diagram`, `update_diagram`, `get_diagram`, `create_board`, `add_diagram_to_board`, `get_board`. Boards are the primary workspace; use whichever transport (URL, REST, MCP) suits your agent.

---

## Rules

1. Save the `secret` from create — returned once, required for REST updates.
2. Content must be valid Mermaid syntax.
3. Each update creates a new version. Nothing is ever overwritten.
4. Return the `url` field to the user. It is the board URL when available. Never return the endpoint URL you called.
5. Diagrams are free, public, and permanent. Anyone with the URL can view.
