{
  "openapi": "3.1.1",
  "info": {
    "title": "Safe Markdown Preview API",
    "version": "1.0.0",
    "summary": "Render Markdown with GFM tables and footnotes to sanitised HTML and see what was removed.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://safemarkdownpreview.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Render Markdown with GFM tables and footnotes to sanitised HTML and see what was removed.",
        "description": "Renders Markdown with headings, emphasis, lists, task lists, code, GFM tables, links, images, blockquotes and footnotes into HTML built from a strict allow-list. Raw HTML can be escaped, stripped or sanitised; scripts, event handlers, style attributes and javascript:/vbscript:/data: URLs are removed and listed in a security report. Shows a preview, the sanitised HTML source and a plain-text version.\n\nMethodology:\n- A two-pass parser written for this tool: link-reference and footnote definitions are collected first, then blocks (ATX/setext headings, fenced and indented code, blockquotes, nested bullet/ordered lists with task items, GFM tables with alignment, thematic breaks, HTML blocks, paragraphs) and inline syntax (code spans, emphasis, strikethrough, links, images, autolinks, hard breaks, backslash escapes). It follows CommonMark closely but is not a full conformance implementation; rare edge cases (complex emphasis nesting, lazy continuation inside nested lists) can differ.\n- All output HTML is generated by the renderer from an allow-list (p, h1–h6, lists, tables, code, a, img, blockquote, …). Text and attribute values are always escaped. Raw HTML is escaped, stripped or passed through a tokenizer that rebuilds only allow-listed tags with allow-listed attributes; script/style/iframe/object/svg and similar elements are removed with their content; on* handlers, style, id and class attributes are dropped.\n- URLs (Markdown links, images, href/src attributes) are decoded (entities, escapes) and stripped of control and whitespace characters before the scheme check, so obfuscations like “java&#x09;script:” are caught. Allowed: http, https, mailto, tel and relative URLs; data: only for base64 raster images when enabled. Links get rel=\"nofollow noopener noreferrer\".\n- Finally the HTML is balanced (stray closing tags dropped, open tags closed) so raw HTML cannot break out of the preview container. The page’s Content-Security-Policy blocks scripts and external images anyway; the preview is rendered in the browser from the sanitised HTML only.",
        "tags": [
          "developer"
        ],
        "x-mcp-tool": "safe_markdown_preview",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SafeMarkdownPreviewInput"
              },
              "examples": {
                "example1": {
                  "summary": "Release notes with table, tasks and footnote",
                  "value": {
                    "markdown": "# Release notes\n\nVersion **2.4** brings _faster_ parsing, `inline code` and ~~old bugs~~ fixes.[^perf]\n\n- [x] Tables (GFM)\n- [ ] Footnotes\n- Links: [docs](https://example.com/docs \"Documentation\") and <https://usefulweb.site>\n\n| Feature | Status | Notes |\n|:--------|:------:|------:|\n| Tables  | ✓      | GFM   |\n| Images  | ✓      | allow-listed |\n\n> Quote with a [reference link][ref].\n\n```js\nconsole.log(\"code is escaped: <b>\");\n```\n\n[ref]: https://commonmark.org/\n[^perf]: Measured on a 1 MB document.\n"
                  }
                },
                "example2": {
                  "summary": "XSS attempts are removed and reported",
                  "value": {
                    "markdown": "# Hello <img src=x onerror=alert(1)>\n\n<script>alert('xss')</script>\n\nClick [me](javascript:alert(document.cookie)) or [this](JaVaScRiPt&#58;alert(1)) or ![pixel](data:image/svg+xml;base64,PHN2Zz4=).\n\n<div onclick=\"steal()\" style=\"position:fixed\">A <b>bold</b> div with <a href=\"vbscript:msgbox(1)\">a link</a></div>\n\n<iframe src=\"https://evil.example\"></iframe>\n\n<a href=\"https://example.com\" onmouseover=\"x()\">safe link, unsafe handler</a>\n"
                  }
                },
                "example3": {
                  "summary": "Same input with raw HTML escaped",
                  "value": {
                    "markdown": "# Hello <img src=x onerror=alert(1)>\n\n<script>alert('xss')</script>\n\nClick [me](javascript:alert(document.cookie)) or [this](JaVaScRiPt&#58;alert(1)) or ![pixel](data:image/svg+xml;base64,PHN2Zz4=).\n\n<div onclick=\"steal()\" style=\"position:fixed\">A <b>bold</b> div with <a href=\"vbscript:msgbox(1)\">a link</a></div>\n\n<iframe src=\"https://evil.example\"></iframe>\n\n<a href=\"https://example.com\" onmouseover=\"x()\">safe link, unsafe handler</a>\n",
                    "rawHtml": "escape"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SafeMarkdownPreviewResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "SafeMarkdownPreviewInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "markdown": {
            "title": "Markdown",
            "type": "string",
            "maxLength": 1000000
          },
          "file": {
            "title": "Or upload a Markdown file",
            "type": "object",
            "description": "Upload as {filename, contentType?, contentBase64}. Max 977 KiB. Accepts .md,.markdown,.txt,text/markdown,text/plain.",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "rawHtml": {
            "title": "Raw HTML in the Markdown",
            "type": "string",
            "enum": [
              "sanitize",
              "escape",
              "strip"
            ],
            "default": "sanitize"
          },
          "gfm": {
            "title": "GitHub extensions (tables, task lists, strikethrough, bare URLs)",
            "type": "boolean",
            "default": true
          },
          "footnotes": {
            "title": "Footnotes ([^1])",
            "type": "boolean",
            "default": true
          },
          "allowDataImages": {
            "title": "Allow base64 data: images (PNG/JPEG/GIF/WebP/AVIF; never SVG)",
            "type": "boolean",
            "default": false
          }
        },
        "required": []
      },
      "SafeMarkdownPreviewResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "html",
              "text",
              "removed",
              "removedCounts",
              "stats",
              "toc",
              "policy"
            ],
            "properties": {
              "html": {
                "type": "string",
                "description": "Sanitised HTML (allow-listed elements and attributes only)"
              },
              "text": {
                "type": "string"
              },
              "removed": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "kind",
                    "detail"
                  ],
                  "properties": {
                    "kind": {
                      "type": "string"
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              },
              "removedCounts": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "stats": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "toc": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "level",
                    "text",
                    "id"
                  ],
                  "properties": {
                    "level": {
                      "type": "integer"
                    },
                    "text": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    }
                  }
                }
              },
              "policy": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "rawHtml",
                  "gfm",
                  "footnotes",
                  "allowDataImages",
                  "allowedTags"
                ],
                "properties": {
                  "rawHtml": {
                    "type": "string"
                  },
                  "gfm": {
                    "type": "boolean"
                  },
                  "footnotes": {
                    "type": "boolean"
                  },
                  "allowDataImages": {
                    "type": "boolean"
                  },
                  "allowedTags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}