markdown-to-pdf

Convert a Markdown file to a styled PDF document. The Markdown is first rendered to HTML with tables, code highlighting, and table of contents support, then converted to PDF using WeasyPrint. Supports custom page sizes, margins, headers, footers, and grayscale output.


Endpoint

POST /v1/convert/markdown-to-pdf

Content-Type: multipart/form-data

Accepted input: .md or .markdown files (UTF-8 encoded)

Output format: .pdf (application/pdf)


Authentication

Requires either a private API key or a JWT token from a public key.

X-API-Key: sk_live_your_private_key

Or:

Authorization: Bearer <jwt_token>

Request Parameters

Parameter Type Required Default Description
file file Yes -- The .md or .markdown file to convert. Must be UTF-8 encoded.
output_filename string No Input filename Custom output filename. The .pdf extension is added automatically.
direct_download boolean No true When true, returns raw PDF bytes. When false, returns JSON metadata with a presigned download URL.
pdf_options string No null JSON string containing PDF configuration options. See below.

PDF Options

Pass as a JSON string in the pdf_options form field. All fields are optional.

Parameter Type Default Description
page_size string "A4" Named page size.
page_width float null Custom page width in millimeters. Both width and height must be set together.
page_height float null Custom page height in millimeters.
orientation string "portrait" "portrait" or "landscape".
margins object {"top": 10, "bottom": 10, "left": 10, "right": 10} Page margins in millimeters.
grayscale boolean false Convert the output to grayscale.
header object null Page header. Format: {"content": "<text>", "height": 15}.
footer object null Page footer. Same format as header.

Supported page sizes: A0-A6, B0-B5, Letter, Legal, Tabloid, Ledger

Header/footer template variables: {{page}}, {{total_pages}}, {{date}}, {{title}}, {{url}}


Conversion Details

The conversion is a two-step process:

  1. Markdown to HTML using Python-Markdown with extensions:
  2. tables -- pipe-delimited tables
  3. fenced_code -- triple-backtick code blocks
  4. codehilite -- syntax highlighting
  5. toc -- table of contents via [TOC] marker
  6. attr_list -- HTML attributes via {.class #id} syntax

  7. HTML to PDF using WeasyPrint with a built-in stylesheet that provides:

  8. System font stack, 800px max-width centered layout
  9. Styled code blocks, tables, blockquotes, and images
  10. Responsive image sizing (max-width: 100%)

The pdf_options are injected as CSS @page rules before rendering.


Response

Direct Download (direct_download=true, default)

HTTP 200 OK
Content-Type: application/pdf
Content-Disposition: inline; filename="readme_20260405_123456789.pdf"

Metadata Response (direct_download=false)

{
    "presigned_url": "https://spaces.example.com/...",
    "object_key": "env/files/{project_id}/markdown-to-pdf/readme_20260405_123456789.pdf",
    "filename": "readme_20260405_123456789.pdf",
    "file_size": 34567,
    "conversion_time_seconds": 0.8
}

Code Examples

Python

import requests
import json

with open("README.md", "rb") as f:
    response = requests.post(
        "https://api.enconvert.com/v1/convert/markdown-to-pdf",
        headers={"X-API-Key": "sk_live_your_private_key"},
        files={"file": ("README.md", f, "text/markdown")},
        data={
            "pdf_options": json.dumps({
                "page_size": "Letter",
                "margins": {"top": 25, "bottom": 25, "left": 20, "right": 20},
                "footer": {"content": "Page {{page}} of {{total_pages}}", "height": 10}
            })
        }
    )

with open("README.pdf", "wb") as out:
    out.write(response.content)

Node.js

const form = new FormData();
form.append("file", fs.createReadStream("README.md"));
form.append("pdf_options", JSON.stringify({
    page_size: "Letter",
    footer: { content: "Page {{page}} of {{total_pages}}", height: 10 }
}));

const response = await fetch("https://api.enconvert.com/v1/convert/markdown-to-pdf", {
    method: "POST",
    headers: { "X-API-Key": "sk_live_your_private_key" },
    body: form
});

fs.writeFileSync("README.pdf", Buffer.from(await response.arrayBuffer()));

PHP

$ch = curl_init("https://api.enconvert.com/v1/convert/markdown-to-pdf");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["X-API-Key: sk_live_your_private_key"],
    CURLOPT_POSTFIELDS => [
        "file" => new CURLFile("README.md", "text/markdown"),
        "pdf_options" => json_encode(["page_size" => "Letter", "grayscale" => true])
    ]
]);
$pdf = curl_exec($ch);
curl_close($ch);
file_put_contents("README.pdf", $pdf);

Go

body := &bytes.Buffer{}
writer := multipart.NewWriter(body)

part, _ := writer.CreateFormFile("file", "README.md")
file, _ := os.Open("README.md")
io.Copy(part, file)

writer.WriteField("pdf_options", `{"page_size":"Letter","grayscale":true}`)
writer.Close()

req, _ := http.NewRequest("POST", "https://api.enconvert.com/v1/convert/markdown-to-pdf", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("X-API-Key", "sk_live_your_private_key")
resp, _ := http.DefaultClient.Do(req)

Error Responses

Status Condition
400 Bad Request File is not a .md or .markdown file
400 Bad Request Invalid Markdown encoding (expected UTF-8)
400 Bad Request Markdown to PDF conversion failed
400 Bad Request Invalid pdf_options JSON
401 Unauthorized Missing or invalid API key / JWT token
402 Payment Required Monthly conversion limit reached
402 Payment Required Storage limit reached
413 Payload Too Large File exceeds plan's maximum file size

Limits

Limit Value
Max file size Plan-dependent (Free: 5 MB)
Input encoding UTF-8 only
Accepted extensions .md, .markdown
Header/footer content 2000 characters max
Monthly conversions Plan-dependent