commercial investigation
How to Build a Markdown-to-PDF API for SaaS and Automation
Design a production Markdown-to-PDF API with uploads, asynchronous jobs, idempotency, progress, secure artifacts, and reusable browser workers.
Convert Markdown to PDFA production Markdown-to-PDF API should use an asynchronous job lifecycle rather than one large request that uploads content, launches a browser, renders a PDF, stores it, and returns the result before the connection times out.
The reliable pattern is:
Upload source
→ Create job
→ Start or enqueue render
→ Track progress
→ Validate PDF
→ Store artifact
→ Return expiring preview/download URLs
This structure separates client retries, storage, rendering, and artifact access.
Need the content-rendering overview first? Read the complete Markdown-to-PDF guide.
Why not send Markdown directly to one synchronous endpoint?
A synchronous endpoint is simple for a demo, but production exports can wait for:
- remote images;
- web fonts;
- MathJax;
- Mermaid;
- large tables;
- many pages;
- browser startup;
- storage writes.
The request may exceed a gateway or client timeout even when rendering is healthy. A retry can also create duplicate exports.
An asynchronous job gives the client a stable resource to query.
What should the API lifecycle look like?
1. Upload the source
Upload .md or .txt to object storage or a controlled upload endpoint.
Benefits:
- conversion requests stay small;
- source access can expire;
- upload validation is separate from rendering;
- workers can fetch input without holding the client connection.
2. Create the export job
The job record should include:
- source reference;
- requested page format;
- orientation;
- margins;
- title;
- page-number setting;
- tenant/user ownership;
- status;
- progress;
- current step;
- timestamps;
- expiry policy.
SolConverter currently uses states equivalent to:
awaiting_start;processing;completed;failed.
3. Start or enqueue rendering
The API can start immediately or place the job on a queue.
The worker should resolve the source, validate limits, launch or acquire a browser context, render the document, validate the output, and store the artifact.
4. Track progress
Progress should represent real stages rather than a cosmetic timer.
Example stages:
validating_source
parsing_markdown
rendering_math
rendering_diagrams
loading_images
printing_pdf
validating_pdf
storing_artifact
completed
Not every stage needs an exact percentage. A truthful current step is more useful than a false precision estimate.
5. Return artifact URLs
Separate preview and download URLs when their behavior differs.
Use expiring URLs so artifacts are not permanently public. Expiry alone is not authorization; the job and storage layer must still enforce ownership and access policy.
Why do create and start operations need idempotency?
Clients retry requests when they see a timeout, dropped connection, or uncertain response.
Without idempotency, the same user action can create multiple jobs and multiple PDFs.
An idempotency key lets the server return the original result for a repeated logical request.
Apply idempotency to operations such as:
- create export;
- start export;
- payment-linked generation;
- webhook-triggered report creation.
Store the key with enough request identity to detect conflicting reuse.
Should the API accept raw Markdown in JSON?
It can, but large Markdown should usually use an upload flow.
Reasons:
- request-size limits;
- easier checksum and file validation;
- resumable upload options;
- reduced duplication during retries;
- clearer source expiry;
- support for future asset bundles.
SolConverter currently accepts source through upload flow and restricts Markdown exports to .md and .txt.
How should browser workers be managed?
Launching a fresh Chromium process for every file creates avoidable startup and memory cost.
A backend can reuse a persistent browser process while creating isolated contexts or pages per job.
Controls still needed:
- clear document state;
- isolate cookies/storage;
- reset MathJax macros and labels;
- close pages after jobs;
- enforce per-job timeouts;
- restart unhealthy workers;
- restart when renderer source changes.
SolConverter currently reuses Chrome and a persistent Node bridge, and restarts the bridge when renderer source changes.
What output validation should happen?
Do not treat “the browser returned bytes” as proof of a valid artifact.
Check:
- output begins with the
%PDF-signature; - file size is within configured limits;
- render did not return HTML or an error page;
- storage write completed;
- optional syntax/stream validation in test or asynchronous QA;
- job metadata matches the artifact.
The current SolConverter pipeline validates the PDF signature and size before storage. Its internal test output has also been checked with qpdf --check.
How should errors be represented?
Return a stable job-level error model.
Useful fields:
{
"code": "RENDER_TIMEOUT",
"message": "The document exceeded the render time limit.",
"step": "rendering_diagrams",
"retryable": true
}
Avoid returning raw browser stack traces to end users. Preserve detailed logs internally with tenant-safe redaction.
Separate:
- invalid input;
- unsupported feature;
- blocked resource;
- render timeout;
- internal renderer failure;
- storage failure;
- authorization failure.
How do you prevent one broken block from failing the job?
Handle recoverable content errors inside the renderer:
- malformed equation → source fallback;
- invalid Mermaid → diagram fallback;
- missing image → placeholder;
- unsupported code language → plain source.
Reserve job failure for document-level problems such as:
- unreadable source;
- exceeded hard size limit;
- browser crash;
- render timeout;
- invalid final PDF;
- storage failure.
This distinction improves successful completion without hiding local problems.
Read: How to make large Markdown-to-PDF exports reliable.
What security controls belong in the API?
The API, renderer, and storage layer share responsibility.
Minimum controls:
- authenticate and authorize job access;
- validate extension, type, and size;
- sanitize rendered HTML;
- block unsafe schemes and private/local resource URLs;
- restrict browser requests;
- use CSP;
- limit render time;
- limit input and output size;
- expire source and artifact access;
- isolate tenants;
- redact sensitive logs;
- verify final file type.
Read the full guide: How to safely convert untrusted Markdown to PDF.
Generic API example
The exact SolConverter endpoints were not provided, so this example is intentionally generic.
Upload source
POST /v1/uploads
Content-Type: multipart/form-data
[email protected]
Create job
POST /v1/markdown-pdf-jobs
Idempotency-Key: 4f21...
{
"source_id": "src_123",
"page_size": "A4",
"orientation": "portrait",
"margins_mm": {
"top": 15,
"right": 15,
"bottom": 15,
"left": 15
},
"page_numbers": true
}
Get status
GET /v1/markdown-pdf-jobs/job_123
{
"id": "job_123",
"status": "processing",
"progress": 72,
"current_step": "rendering_diagrams"
}
Completed job
{
"id": "job_123",
"status": "completed",
"preview_url": "https://files.example/preview/...",
"download_url": "https://files.example/download/...",
"expires_at": "2026-08-05T12:00:00Z"
}
These examples use illustrative endpoints and values rather than a published SolConverter API contract.
When should you use an API instead of local tooling?
Use local tooling when:
- one developer controls the environment;
- files are converted manually or in a local build;
- user-upload security is not required;
- install size and dependencies are acceptable.
Use an API when:
- multiple applications need the same renderer;
- users upload documents;
- exports are asynchronous or scheduled;
- you need consistent versions;
- you need progress and auditability;
- you need expiring artifacts;
- you do not want every client to manage Chromium and math/diagram dependencies.
Frequently asked questions
Should a Markdown-to-PDF API be synchronous?
Only for small, predictable documents. Asynchronous jobs are safer for technical documents and external resources.
What does an idempotency key prevent?
It prevents duplicate logical operations when a client retries an uncertain request.
Can the browser process be reused?
Yes, but each job must still be isolated and state must be reset.
Should preview and download URLs expire?
Usually yes. The exact duration should follow product, compliance, and user-experience requirements.
How do I know the output is a PDF?
Validate the file signature and size, and use deeper PDF validation in testing or QA.
Next step
Define the job contract before implementing the UI. A clean lifecycle makes progress reporting, retries, billing, monitoring, and support easier.
Try the Markdown-to-PDF tool or review the security architecture for untrusted Markdown.