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:
- Markdown to HTML using Python-Markdown with extensions:
tables-- pipe-delimited tablesfenced_code-- triple-backtick code blockscodehilite-- syntax highlightingtoc-- table of contents via[TOC]marker-
attr_list-- HTML attributes via{.class #id}syntax -
HTML to PDF using WeasyPrint with a built-in stylesheet that provides:
- System font stack, 800px max-width centered layout
- Styled code blocks, tables, blockquotes, and images
- 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 |