Markdown to Image API: Generate PNGs with cURL, Node.js, Python, and n8n
Manual export is fine for one image. It becomes a bottleneck when your product needs to turn every AI report, changelog, support answer, or social post into a consistent visual. A Markdown-to-image API moves that render step into code: send Markdown, choose a format and theme, receive a URL or binary file, and store the result where your application needs it.
This guide builds a real integration with the MarkdownToImage API. You will start with cURL, then implement the same workflow in Node.js and Python, configure n8n, and add the production safeguards that quick-start snippets often omit.
The endpoint and limits in this article were checked against the official API documentation on August 2, 2026. Quotas and plans can change, so verify the current pricing page before estimating production cost.
Use an API when image generation is part of a repeatable workflow rather than an occasional design task. Common examples include:
- turning an LLM response into a downloadable report image;
- generating release-note cards after a deployment;
- creating Open Graph or social cards from CMS fields;
- rendering code, tables, Mermaid diagrams, or KaTeX formulas consistently;
- producing visual artifacts inside n8n, Dify, Make, CI, or an internal tool.
A browser converter is still faster for a one-off export. The API becomes valuable when the input already exists in another system, the same styling must be applied many times, or the output must continue automatically to storage, publishing, or messaging.
Sign in to MarkdownToImage and create an API token from the API Tokens area. Treat it like a password: do not put it in source code, Markdown files, screenshots, client-side JavaScript, or workflow exports.
For local testing, expose it as an environment variable:
export MARKDOWN_TO_IMAGE_API_TOKEN="mti_your_token_here"
In production, use your hosting platform’s encrypted secret store. If a token appears in a public repository or log, revoke it and create a new one rather than trying to hide the old commit.
The API currently offers a monthly free quota with watermarked output. Watermark-free and higher-volume usage depends on credits or the current plan. Check the official API and pricing pages at implementation time instead of hard-coding a quota assumption into your product.
The generation endpoint accepts JSON and uses Bearer authentication. This request creates an 1200-pixel-wide PNG with a dark GitHub-style theme:
curl -X POST https://markdowntoimage.com/api/v1/images/generate \
-H "Authorization: Bearer $MARKDOWN_TO_IMAGE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"markdown": "# Weekly release\n\n- Faster exports\n- Clearer reports\n- One automated workflow",
"format": "png",
"width": 1200,
"quality": 2,
"theme": "github-dark",
"mode": "url"
}'
A successful URL-mode response contains data.imageUrl. The URL is temporary, so opening it in a browser proves the render worked but does not create permanent storage. Download the file promptly and upload it to your own object storage, CMS, or media library.
The request can be simple—only markdown is required—but explicit settings make automated output predictable.
| Parameter | What it controls | Practical choice |
|---|---|---|
markdown | Source content | Validate length and required sections before sending |
format | png, jpeg, webp, or pdf | PNG for crisp UI and code; WebP for smaller web assets; PDF for documents |
width | Render width from 200 to 2560 pixels | 1200 for social and report cards; test your real layout |
quality | Device scale factor from 1 to 3 | Start at 2; higher values increase output size and render work |
theme | Overall visual theme | Pin a named theme so releases do not change appearance unexpectedly |
codeStyle | Syntax-highlighting palette | Choose for contrast with the page theme |
fontFamily | Font family preset | Test all languages your product will render |
mode | url or binary response | URL for easy orchestration; binary for direct file pipelines |
Use JPEG for photographic content where lossy compression is acceptable. Use PNG for code, diagrams, and sharp interface elements. Use WebP when bandwidth matters and every consumer supports it. Although PDF uses the same generation endpoint, it is a document output rather than a normal social image.
The following Node.js script calls the API, checks the HTTP response, downloads the temporary result, and writes a real file. It uses the global fetch API available in current Node.js releases.
import { writeFile } from "node:fs/promises";
const token = process.env.MARKDOWN_TO_IMAGE_API_TOKEN;
if (!token) throw new Error("MARKDOWN_TO_IMAGE_API_TOKEN is required");
const response = await fetch("https://markdowntoimage.com/api/v1/images/generate", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
markdown: "# Build report\n\n- Tests: **passed**\n- Deploy: **ready**",
format: "png",
width: 1200,
quality: 2,
theme: "github-dark",
mode: "url",
}),
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) {
throw new Error(`Generation failed: ${response.status} ${await response.text()}`);
}
const result = await response.json();
const imageResponse = await fetch(result.data.imageUrl, {
signal: AbortSignal.timeout(30_000),
});
if (!imageResponse.ok) {
throw new Error(`Download failed: ${imageResponse.status}`);
}
await writeFile("build-report.png", Buffer.from(await imageResponse.arrayBuffer()));
console.log("Saved build-report.png");
For a web service, return only after your permanent upload finishes. Recording the API response URL without copying the file creates a delayed failure when that temporary URL expires.
Python follows the same two-request pattern in URL mode: create the render, then download it. The explicit timeouts prevent a worker from hanging indefinitely.
import os
from pathlib import Path
import requests
token = os.environ["MARKDOWN_TO_IMAGE_API_TOKEN"]
payload = {
"markdown": "# Build report\n\n- Tests: **passed**\n- Deploy: **ready**",
"format": "png",
"width": 1200,
"quality": 2,
"theme": "github-dark",
"mode": "url",
}
response = requests.post(
"https://markdowntoimage.com/api/v1/images/generate",
headers={"Authorization": f"Bearer {token}"},
json=payload,
timeout=30,
)
response.raise_for_status()
image_url = response.json()["data"]["imageUrl"]
image_response = requests.get(image_url, timeout=30)
image_response.raise_for_status()
Path("build-report.png").write_bytes(image_response.content)
print("Saved build-report.png")
Wrap this function with your job queue rather than running a long render inside a user-facing request when volume grows. A queue gives you controlled concurrency, retries, and a place to record the final storage URL.
In n8n, place an HTTP Request node after the node that creates or fetches your Markdown. Store the token in n8n Credentials rather than pasting it directly into the workflow JSON.
Method: POST
URL: https://markdowntoimage.com/api/v1/images/generate
Authentication: Header Auth
Header name: Authorization
Header value: Bearer {{$credentials.markdownToImageToken}}
Send Body: JSON
Body:
{
"markdown": "{{$json.content}}",
"format": "png",
"width": 1200,
"quality": 2,
"theme": "github-dark",
"mode": "url"
}
Connect a second HTTP Request node to download {{$json.data.imageUrl}}, enable file output, and then send that binary file to S3, Google Drive, Directus, WordPress, Slack, or your next destination. Configure the error branch explicitly so authentication or quota failures do not silently publish an empty record.
Choose URL mode when your automation platform handles JSON easily and can perform a second download. It is convenient for queues, webhooks, and no-code workflows, but the returned URL is retained temporarily—currently 24 hours according to the official documentation.
Choose binary mode when you want one response that can be streamed directly into storage. It removes the second HTTP request, but your client must handle binary content, content type, file extension, memory limits, and partial-upload cleanup correctly.
Whichever mode you use, save the final asset in storage you control. Also store useful metadata beside it: requested format, width, theme, source content ID, generation timestamp, and a hash of the Markdown. That makes duplicate detection and incident diagnosis much easier.
The API documents these important outcomes:
| HTTP status | Meaning | Recommended action |
|---|---|---|
400 | Missing Markdown or invalid parameters | Do not retry unchanged input; validate the payload |
401 | Missing, invalid, or revoked token | Stop and alert; rotate or correct the secret |
429 | Free quota or credits are unavailable | Delay the job, notify the owner, or upgrade capacity |
500 | Generation or internal failure | Retry with backoff a limited number of times |
Automatic retries should target transient failures, not every non-200 response. Use exponential backoff with jitter for 500 responses and network timeouts. Before retrying after an uncertain connection failure, check whether your application already stored an output for the same content hash. This prevents one logical job from creating several paid renders.
Log the status code, provider error code, job ID, attempt number, and latency—but never log the full Authorization header.
Before sending real traffic, verify these points:
- keep the API token in an encrypted server-side secret store;
- set connection and total request timeouts;
- cap retries and add exponential backoff with jitter;
- limit worker concurrency to match your quota and downstream storage;
- validate Markdown size and required fields before calling the API;
- pin format, width, theme, code style, and font settings;
- download URL-mode results immediately to permanent storage;
- derive safe, deterministic filenames rather than trusting user input;
- add alt text and meaningful surrounding copy when publishing generated images;
- record usage and alert before quota exhaustion;
- test multilingual fonts, long code lines, tables, Mermaid, and KaTeX with your real content;
- keep a manual fallback for business-critical publishing workflows.
Start with a small representative test set. Ten realistic Markdown samples reveal more layout problems than hundreds of “Hello World” calls.
Yes. Send format: "png" to the generation endpoint. You can also request JPEG, WebP, or PDF. PNG is usually the safest choice for code, text, diagrams, and interface screenshots.
Do not expose a secret API token in client-side code. Call the API from your server, serverless function, or protected automation workflow, then return the stored result to the browser.
Pass the model’s Markdown output into the markdown field after applying your own length, content, and safety checks. A stable prompt template plus pinned render settings produces more consistent cards than allowing every response to choose its own layout.
n8n is enough for straightforward generation, download, and upload workflows. Use application code when you need high concurrency, custom idempotency, detailed observability, or tight integration with product permissions and billing.
Take one piece of Markdown your product already produces and run the cURL example against it. Inspect code wrapping, tables, fonts, and image dimensions before expanding the workflow.
When the first render looks right, open the MarkdownToImage API guide, create a server-side token, and move the Node.js, Python, or n8n recipe into a small production test. The safest rollout is one real content type, one fixed visual preset, permanent storage, and observable error handling—then scale from evidence.