# Markdown Studio > Convert Markdown to PDF, DOCX, or HTML with fonts embedded, by building a URL that carries the document. A conversion is one stateless request: the Markdown travels compressed in the `data` query parameter, so the URL is the entire request. `GET /export` returns the converted file. `GET /` opens the same document in the editor, which previews it in real time. Paths resolve against the origin serving this file, including its port. ## Encoding the document Encode the Markdown as UTF-8, compress it with Brotli at quality 11, then encode those bytes as base64url: `-` and `_` replace `+` and `/`, and `=` padding is dropped. Raw Markdown, JSON, gzip, zlib, and padded base64 are rejected with `400`. Always generate `data` with code; never write one by hand. | Parameter | Values | Requirement | | --- | --- | --- | | `v` | `1` | Required protocol version. | | `data` | Brotli-compressed, base64url Markdown | Required. | | `format` | `pdf`, `html`, `tex`, `docx` | `/export` only; defaults to `pdf`. | | `font` | `serif`, `sans` | Defaults to `serif`. | | `embed` | `1` or `0` | Defaults to `1`, which embeds fonts so the file renders the same everywhere. | | `render` | `8` | Current cache revision; include it. | Do not repeat a parameter. Successful exports return file bytes, so save PDF and DOCX responses as binary. Status `400` means the query is malformed, `413` that a limit was exceeded, `422` that conversion failed, and `503` that converters are busy; honour `Retry-After` and do not resend an unchanged rejected request. ## URL length limit `data` is capped at 2097152 characters (2 MiB) after compression and encoding. Browsers, proxies, and chat clients cap URLs far lower: keep links near 2000 characters for anything a person will paste, and expect intermediaries to reject much beyond 8000. When a document does not fit, do not truncate it — send it as a body instead with `POST /export` and `{"markdown", "format", "font", "native"}`, where `native: false` matches `embed=1`, then save the response bytes. ## TypeScript ```ts import {brotliCompressSync, constants} from 'node:zlib' export function documentUrls(markdown: string, origin: string, format = 'pdf') { const packed = brotliCompressSync(Buffer.from(markdown, 'utf8'), { params: {[constants.BROTLI_PARAM_QUALITY]: 11}, }) const data = packed.toString('base64url') if (data.length > 2097152) throw new Error('Too large for a URL; use POST /export.') const query = new URLSearchParams({v: '1', data, font: 'serif', embed: '1', render: '8'}) const editor = new URL('/', origin) editor.search = query.toString() query.set('format', format) const file = new URL('/export', origin) file.search = query.toString() return {editor: editor.href, export: file.href} } ``` ## Python ```python # pip install brotli import base64 import brotli from urllib.parse import urlencode, urlsplit, urlunsplit def document_urls(markdown: str, origin: str, format: str = "pdf") -> dict[str, str]: packed = brotli.compress(markdown.encode("utf-8"), quality=11) data = base64.urlsafe_b64encode(packed).decode("ascii").rstrip("=") if len(data) > 2097152: raise ValueError("Too large for a URL; use POST /export.") query = {"v": "1", "data": data, "font": "serif", "embed": "1", "render": "8"} parts = urlsplit(origin) def build(path: str, **extra: str) -> str: return urlunsplit((parts.scheme, parts.netloc, path, urlencode({**query, **extra}), "")) return {"editor": build("/"), "export": build("/export", format=format)} ``` ## Documentation - [HTTP API and link generation](/docs/tool-use.md): POST bodies, MIME types, filenames, font behaviour, image and math limits, and errors. - [Markdown syntax guide](/docs/markdown.md): Supported syntax with copyable examples. - [Complete documentation](/llms-full.txt): Every document above in one fetch.