{"openapi":"3.1.0","info":{"title":"Markdown to PDF Converter","description":"Open, no-auth API that turns Markdown into a formatted PDF. Send it as a JSON body, as a file upload, or in a query string, and get back a clean paginated document: headings, lists, tables, code, blockquotes and rules. No key, no signup.\n\n## Endpoints\n\nBase URL `https://mdtopdf.tigzig.com`\n\n- `POST /v1/convert/text` - JSON body, returns the PDF directly. Canonical; the MCP tool wraps this one.\n- `POST /v1/convert` - upload a `.md`, `.markdown` or `.txt` file as `multipart/form-data`, returns the PDF directly.\n- `GET /v1/convert/text?content=...` - the same conversion for a client that can only make GET requests. Returns the PDF directly.\n- `POST /v1/convert/text/hosted` - returns `{\"pdf_url\": \"...\"}` instead of the binary, for clients that would rather have a link.\n\n```bash\ncurl -X POST https://mdtopdf.tigzig.com/v1/convert/text \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"content\": \"# Title\\n\\nHello **world**\", \"filename\": \"out.pdf\"}' \\\n  --output out.pdf\n```\n\n```bash\ncurl -X POST https://mdtopdf.tigzig.com/v1/convert -F 'file=@notes.md' --output out.pdf\n```\n\n```bash\ncurl -G https://mdtopdf.tigzig.com/v1/convert/text \\\n  --data-urlencode 'content@document.md' \\\n  --output out.pdf\n```\n\n## Markdown supported\n\n- Headings `h1` to `h6`\n- Bold, italic, strikethrough, inline code\n- Links, clickable in the PDF\n- Bullet and numbered lists with real nesting (two-space indentation works)\n- Task lists\n- Tables, any column count; wide tables shrink to fit rather than failing\n- Fenced code blocks\n- Blockquotes and horizontal rules\n- Greek, maths and currency symbols, and emoji\n\n## Page breaks\n\nMarkdown has no page break, so this API adds one. Put any of `:::pagebreak:::`, `\\pagebreak`, `\\newpage` or `<!-- pagebreak -->` on a line by itself and the content after it starts on a new page. Markers inside fenced code blocks are left alone.\n\n## Symbols\n\nGreek letters, maths and logic operators (`<=`, `>=`, `!=`, sqrt, sum, product, integral, infinity, element-of, subset, union), arrows, typographic punctuation, dashes and curly quotes all render as themselves.\n\nThe dollar, pound, euro, yen and cent signs render as themselves. Every other currency sign is written out instead, so `₹1,950` becomes `Rs.1,950` and `৳350` becomes `BDT 350`. The amount is always preserved and a price never comes out as an empty box.\n\nEmoji are converted to matching symbols, so a checkmark stays a checkmark. Emoji with no close equivalent become a small dot.\n\n## Plain text files\n\nUpload a `.txt` file to `POST /v1/convert` and its single line breaks are kept, so a letter stays laid out the way it was written. A `.md` file follows standard Markdown rules, where a single newline is just a space.\n\n## Not supported\n\nListed so you can plan around it. None of these fail the conversion.\n\n- Images are skipped; the surrounding text is unaffected.\n- Non-Latin scripts (Devanagari, Arabic, CJK, Cyrillic) render as filled boxes.\n- Raw HTML tags are ignored, though their text is kept.\n\n## Sending your Markdown\n\nFor the JSON and file forms, send UTF-8. JSON requires it (RFC 8259).\n\nFor the `GET` form your Markdown goes in the query string and must be URL-encoded. Any HTTP client does this for you in one call - `params={'content': md}` in Python requests, `URLSearchParams` in JavaScript, `--data-urlencode` in curl.\n\n**Encode a literal plus sign as `%2B`.** A bare `+` in a query string means a space, so `C++` would arrive as `C  ` and your document would be changed with no error raised. Client libraries get this right; hand-built URLs are where it goes wrong.\n\n## Limits\n\n- **10 requests per minute** and **200 per day**, per IP.\n- **1 MB** per document on the JSON and file forms, roughly 400 pages of prose.\n- **14,000 bytes** of query string on the `GET` form, after URL-encoding. That is roughly 10,000 characters of prose or 7,000 of tables. Use `POST` for anything larger.\n- **50,000 characters** in a single paragraph with no blank line in it. Split it with a blank line, or use `POST`.\n- **15 seconds** per conversion. Almost everything finishes in a few seconds.\n\nEvery response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, and a `429` carries `Retry-After` in seconds. Concurrent requests are queued rather than refused, so a burst waits briefly and you do not need to serialise your calls.\n\n## Errors\n\nEvery `4xx` and `5xx` returns the same envelope:\n\n```json\n{\"error\": {\"code\": \"...\", \"message\": \"...\", \"path\": \"...\", \"example\": \"...\"}, \"help\": {}}\n```\n\n`code` is a stable slug you can branch on, `message` says what to change, and `example` is a curl line that works. Malformed JSON is a `400`; well-formed JSON that fails the schema is a `422`.\n\nThe slugs: `INVALID_JSON_BODY`, `INVALID_REQUEST_BODY`, `FILE_NOT_TEXT`, `DOCUMENT_TOO_LARGE`, `URL_CONTENT_TOO_LONG`, `PARAGRAPH_TOO_LONG`, `CONVERSION_TIMEOUT`, `SERVICE_BUSY`, `STORAGE_BUDGET_EXCEEDED`, `NOT_FOUND`, `ENDPOINT_NOT_FOUND` on a convert-like path, `METHOD_NOT_ALLOWED`, `RATE_LIMITED`.\n\n## MCP server\n\n`https://mdtopdf.tigzig.com/mcp` over Streamable HTTP, tool `convert_markdown_to_pdf`, which returns a hosted PDF URL rather than the binary. Add it as a custom connector in Claude, ChatGPT, Cursor, LM Studio or n8n.\n\n## More Tigzig tools\n\nFree, no auth: market and fund data, macro and credit indicators, portfolio and risk analytics, India corporate filings, SQL databases. Catalog at `https://api.tigzig.com/.well-known/api-catalog`, guide at `https://www.tigzig.com/llms.txt`.","version":"1.2.0"},"paths":{"/v1/convert":{"post":{"summary":"Upload a Markdown or text file, get the PDF file back","description":"Upload a Markdown or plain-text file and get the PDF back in the response body.\n\nSend the file as multipart/form-data under the field name `file`. Accepts .md, .markdown\nand .txt; a .txt is treated as plain text so its single line breaks are preserved, while\n.md follows standard Markdown rules. The response is application/pdf, not JSON, and the\ndownload name is taken from the uploaded filename. A file that is not UTF-8 text (a .pdf\nor .docx, say) returns 400 FILE_NOT_TEXT rather than failing mid-conversion.\n\nSupports headings, bold, italic, strikethrough, inline and fenced code, links, nested and\nnumbered lists, task lists, tables with column alignment, blockquotes, rules, Greek and\nmaths symbols, and the page-break markers described in the API description.","operationId":"convert_markdown_v1_convert_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_convert_markdown_v1_convert_post"}}},"required":true},"responses":{"200":{"description":"The generated PDF, returned as the response body.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/convert/text":{"post":{"summary":"Send Markdown as JSON, get the PDF file back (canonical)","description":"Send Markdown as JSON and get the PDF back in the response body.\n\nBody: {\"content\": \"# your **markdown**\", \"filename\": \"out.pdf\"} - `content` is required,\n`filename` is optional and only names the download. The body must be UTF-8 (RFC 8259).\nThe response is application/pdf, not JSON; if your HTTP client is awkward with binary\nbodies, POST /v1/convert/text/hosted instead and get a URL to the PDF.\n\nSupports headings, bold, italic, strikethrough, inline and fenced code, links, nested and\nnumbered lists, task lists, tables with column alignment, blockquotes, rules, Greek and\nmaths symbols, and the page-break markers described in the API description.","operationId":"convert_markdown_text_endpoint_v1_convert_text_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkdownText"},"examples":{"note":{"summary":"A short note - the smallest thing worth converting","value":{"content":"# Meeting note\n\nAgreed to ship on Friday. **Owner:** Priya.\n","filename":"meeting-note.pdf"}},"report":{"summary":"A short report with a heading, a list and a table","value":{"content":"# Quarterly summary\n\nRevenue held steady while costs fell.\n\n## Highlights\n\n- Costs down 8% quarter on quarter\n- Two new enterprise accounts\n- Churn unchanged at 1.2%\n\n## Numbers\n\n| Metric | Q2 | Q3 |\n|---|---|---|\n| Revenue | 4.10 | 4.15 |\n| Costs | 2.80 | 2.58 |\n\n> Costs fell mainly on the infrastructure move.\n","filename":"quarterly-summary.pdf"}}}}}},"responses":{"200":{"description":"The generated PDF, returned as the response body.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Send Markdown in the URL, get the PDF file back (for GET-only callers)","description":"Send Markdown in the query string and get the PDF back in the response body.\n\nThe same conversion as `POST /v1/convert/text`, reachable by a client that can only\nissue GET requests. Everything else is identical: the same limits, the same rendering,\nthe same errors.\n\n`GET /v1/convert/text?content=%23%20Title%0A%0AHello` returns application/pdf.\n\nTwo things to know. The content limit is smaller than the POST limit because a URL is\nsmaller than a body, so use POST for a long document. And a literal plus sign must be\nsent as %2B, because a bare + in a query string means a space.","operationId":"convert_markdown_text_get_endpoint_v1_convert_text_get","parameters":[{"name":"content","in":"query","required":true,"schema":{"type":"string","description":"Your Markdown, URL-encoded. Every HTTP library does the encoding for you (Python `params={'content': md}`, JS `URLSearchParams`, `curl -G --data-urlencode`). Encode a literal plus sign as %2B - a bare + decodes to a space and would silently alter your document. The whole query string is limited to 14000 bytes AFTER encoding, which is roughly 10,000 characters of prose or 7,000 of tables; POST the same path for anything larger.","title":"Content"},"description":"Your Markdown, URL-encoded. Every HTTP library does the encoding for you (Python `params={'content': md}`, JS `URLSearchParams`, `curl -G --data-urlencode`). Encode a literal plus sign as %2B - a bare + decodes to a space and would silently alter your document. The whole query string is limited to 14000 bytes AFTER encoding, which is roughly 10,000 characters of prose or 7,000 of tables; POST the same path for anything larger."},{"name":"filename","in":"query","required":false,"schema":{"type":"string","description":"Optional. Names the download only.","title":"Filename"},"description":"Optional. Names the download only."}],"responses":{"200":{"description":"The generated PDF, returned as the response body.","content":{"application/json":{"schema":{}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/convert/text/hosted":{"post":{"summary":"Send Markdown as JSON, get a hosted PDF link back (the MCP tool)","description":"Convert Markdown text into a formatted PDF and return a hosted URL to it.\n\nGive it Markdown (including AI-generated Markdown) and get back a URL to a cleanly\nformatted PDF: headings, lists, tables, code, blockquotes, rules. Send a JSON body\n{\"content\": \"# your **markdown**\", \"filename\": \"out.pdf\"} - the text must be UTF-8.\nReturns {\"pdf_url\": \"...\"}. Generated files are served from /static/pdfs and cleaned\nup after 24h. (This is the hosted variant of POST /v1/convert/text - a URL instead of\nthe binary - because an MCP tool result is text and cannot carry a PDF payload.)","operationId":"convert_markdown_to_pdf","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkdownText"},"examples":{"note":{"summary":"A short note - the smallest thing worth converting","value":{"content":"# Meeting note\n\nAgreed to ship on Friday. **Owner:** Priya.\n","filename":"meeting-note.pdf"}},"report":{"summary":"A short report with a heading, a list and a table","value":{"content":"# Quarterly summary\n\nRevenue held steady while costs fell.\n\n## Highlights\n\n- Costs down 8% quarter on quarter\n- Two new enterprise accounts\n- Churn unchanged at 1.2%\n\n## Numbers\n\n| Metric | Q2 | Q3 |\n|---|---|---|\n| Revenue | 4.10 | 4.15 |\n| Costs | 2.80 | 2.58 |\n\n> Costs fell mainly on the infrastructure move.\n","filename":"quarterly-summary.pdf"}}}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_convert_markdown_v1_convert_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_convert_markdown_v1_convert_post"},"ConversionResponse":{"properties":{"pdf_url":{"type":"string","title":"Pdf Url"}},"type":"object","required":["pdf_url"],"title":"ConversionResponse","description":"Response for the hosted (URL-returning) conversion."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"MarkdownText":{"properties":{"content":{"type":"string","title":"Content"},"filename":{"type":"string","title":"Filename","default":"document.pdf"}},"type":"object","required":["content"],"title":"MarkdownText","example":{"content":"# Title\n\nHello world","filename":"out.pdf"}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}