Convert Markdown to PDF
The Markdown-to-PDF endpoint renders Markdown — CommonMark plus the GFM tables and strikethrough extensions — into a PDF document with a built-in print stylesheet. This is the shortest path from a README, a report, or generated notes to a clean PDF, without writing any HTML. Available on every plan.
Endpoint
POST https://api.pdfik.net/markdown-to-pdf
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
markdown | string | Yes | The Markdown source (CommonMark + GFM tables and strikethrough), up to 100,000 characters — longer input is rejected with 422. Raw HTML inside the Markdown is escaped, not rendered — see below. |
webhook_url | string | No | Optional callback URL where the job outcome payload will be POSTed. Credentials must not be embedded in the URL (https://user:pass@host is rejected) — configure delivery headers in the dashboard instead. |
options | object | No | The same options object as /html-to-pdf: paper format, margin, header/footer templates, scale, watermark (Starter+), user_password (Pro+), compression (Pro+). See PDF Options. |
render | object | No | Timeout and wait selectors for browser load states (Pro+ plans only). See Browser Options. |
delivery | object | No | Upload the finished PDF straight to your own bucket via a presigned PUT URL (Pro+ plans only). See Delivery to Your Own Bucket. |
test | boolean | No | Run the job in test mode: full pipeline, no real render, a sample PDF is returned. Quotas are not debited. Defaults to false. |
Markdown Support (markdown)
- Dialect: CommonMark, plus the GFM tables and strikethrough extensions.
- Raw HTML is escaped, not rendered. An
<img>or<div>typed into the Markdown appears in the PDF as literal text. If your document needs HTML or custom CSS, use/html-to-pdf— this endpoint is for pure Markdown. - Styling is the built-in print stylesheet. Headings, tables, code blocks, lists and
blockquotes come out with a consistent document theme; there is no custom CSS
option. Page-level appearance (paper format, margins, header/footer, watermark)
is controlled through
options, exactly like on the other PDF endpoints. - The
einvoiceoption is not available here — an e-invoice needs your own HTML (/html-to-pdf) or the dedicated/einvoice-to-pdfendpoint.
Size Limits
- Input:
markdownaccepts at most 100,000 characters. Longer input is rejected with422validation-error before anything is charged. - Converted document: the HTML produced from the Markdown must fit the processing
queue — the same ~240 KB bound as
/html-to-pdf. Markdown whose converted form exceeds it — a very long document, or one that expands far beyond its own size — is rejected with413payload-too-large-for-queue, and the render is not charged. Split the document into smaller parts.
Delivery to Your Own Bucket (delivery)
Add a delivery object (mode, url) to have the finished PDF uploaded straight to
your own storage via a presigned PUT URL — nothing is stored on PDFik's side: once the
job is done, the job.finished webhook reports the outcome only (no file_url, no
expires_at — we never record where the file went), and the download endpoint
answers 404.
Pro and Business plans. The URL must use https on the standard port 443 (any other
port is rejected with 422). The full rules (URL requirements, expiry, retries, error
behavior) are documented in
Delivery to Your Own Bucket; it
cannot be combined with test mode.
Request Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key (sk_live_...). |
Idempotency-Key | No | 1-255 characters from A-Z a-z 0-9 _ - . :. Retrying a request with the same key returns the original job instead of creating (and billing) a duplicate. Recommended for retry-after-timeout logic. |
The Asynchronous Flow
- Submit Job: Post the Markdown to
/markdown-to-pdf. You'll receive ajob_idand a status ofqueued. - Poll Status: Send
GETrequests to/jobs/{job_id}untilstatusisdone(orfailed). - Download File: Send a request to
/jobs/{job_id}/downloadto stream the PDF binary directly. The file is available for 24 hours after generation (the exact moment is theexpires_atfield in the status response); at most 3 download attempts and 1 active stream are allowed per file. See File Retention & Downloads.
Integration Examples
1. Basic Request (curl)
Markdown travels inside a JSON string, so newlines are escaped as \n:
curl -X POST https://api.pdfik.net/markdown-to-pdf \
-H "X-API-Key: sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"markdown": "# Monthly Report\n\n| Metric | Value |\n| --- | --- |\n| Renders | 1,204 |\n\nGenerated ~~manually~~ automatically.",
"options": { "format": "A4" }
}'
For a Markdown file on disk, build the body from the file instead of quoting it inline:
jq -n --rawfile md report.md '{markdown: $md}' > request.json
curl -X POST https://api.pdfik.net/markdown-to-pdf \
-H "X-API-Key: sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d @request.json
Response (202 Accepted):
{
"job_id": "8bfa5cc3-09ef-436f-80d4-a0eb867123aa",
"status": "queued",
"detail": "Job successfully sent to processing queue."
}
2. JavaScript (ES6 Fetch)
const API_KEY = 'sk_live_YOUR_KEY';
async function markdownToPdf(markdown) {
// 1. Submit the Markdown to start the conversion job
const submitRes = await fetch('https://api.pdfik.net/markdown-to-pdf', {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ markdown })
});
if (!submitRes.ok) throw new Error(`Failed to submit job: ${submitRes.statusText}`);
const { job_id } = await submitRes.json();
// 2. Poll the job status endpoint until it completes or fails
let status = 'queued';
while (status !== 'done' && status !== 'failed') {
await new Promise(resolve => setTimeout(resolve, 2000));
const statusRes = await fetch(`https://api.pdfik.net/jobs/${job_id}`, {
headers: { 'X-API-Key': API_KEY }
});
if (statusRes.status === 429) continue; // rate limited: wait and poll again
if (!statusRes.ok) throw new Error(`Status check failed: ${statusRes.status} ${await statusRes.text()}`);
status = (await statusRes.json()).status;
}
if (status === 'failed') throw new Error('PDF generation job failed');
// 3. Download the PDF file
const downloadRes = await fetch(`https://api.pdfik.net/jobs/${job_id}/download`, {
headers: { 'X-API-Key': API_KEY }
});
if (!downloadRes.ok) throw new Error(`Download failed: ${downloadRes.status} ${await downloadRes.text()}`);
return await downloadRes.arrayBuffer(); // returns PDF binary buffer
}
3. Python (httpx)
import time
import httpx
API_KEY = "sk_live_YOUR_KEY"
BASE_URL = "https://api.pdfik.net"
HEADERS = {"X-API-Key": API_KEY}
def markdown_to_pdf(markdown: str, output_path: str = "output.pdf") -> None:
with httpx.Client(base_url=BASE_URL, headers=HEADERS) as client:
# 1. Submit
submit = client.post("/markdown-to-pdf", json={"markdown": markdown})
submit.raise_for_status()
job_id = submit.json()["job_id"]
# 2. Poll
status = "queued"
while status not in ("done", "failed"):
time.sleep(2)
r = client.get(f"/jobs/{job_id}")
if r.status_code == 429: # rate limited: wait and poll again
continue
r.raise_for_status()
status = r.json()["status"]
if status == "failed":
raise RuntimeError("PDF generation job failed")
# 3. Download
with open(output_path, "wb") as f:
with client.stream("GET", f"/jobs/{job_id}/download") as response:
response.raise_for_status()
for chunk in response.iter_bytes():
f.write(chunk)
markdown_to_pdf("# Hello\n\nRendered from Markdown.")
SDKs and CLI
The official clients cover this endpoint: markdownToPdf in the
JavaScript and Java SDKs,
markdown_to_pdf in the Python SDK, and
pdfik markdown-to-pdf <file.md | -> in the CLI.