Render API
POST /v1/render makes one file per size from a published template and your data. A sync call answers with the file URLs. A job answers at once with an id to poll.
The request
Only template is required. This call sets every field:
curl
curl -X POST "https://mintrender.com/v1/render" \ -H "Authorization: Bearer $MINTRENDER_KEY" \ -H "Content-Type: application/json" \ -d '{"template":"tpl_3k2jQ8xLm0P4nB7vR2sT","version":7,"data":{"title":"Ten ways to sell more","logo":"https://example.com/logo.png"},"format":"png","sizes":["main","square"],"scale":1,"quality":90,"mode":"sync","webhook_url":"https://example.com/hooks/mintrender","metadata":{"order":"1234"}}'JavaScript
const res = await fetch("https://mintrender.com/v1/render", { method: "POST", headers: { Authorization: `Bearer ${process.env.MINTRENDER_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ template: "tpl_3k2jQ8xLm0P4nB7vR2sT", version: 7, data: { title: "Ten ways to sell more", logo: "https://example.com/logo.png", }, format: "png", sizes: ["main", "square"], scale: 1, quality: 90, mode: "sync", webhook_url: "https://example.com/hooks/mintrender", metadata: { order: "1234", }, }),});const body = await res.json();if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);console.log(body);Python
import os import requests res = requests.post( "https://mintrender.com/v1/render", headers={"Authorization": f"Bearer {os.environ['MINTRENDER_KEY']}"}, json={ "template": "tpl_3k2jQ8xLm0P4nB7vR2sT", "version": 7, "data": { "title": "Ten ways to sell more", "logo": "https://example.com/logo.png", }, "format": "png", "sizes": ["main", "square"], "scale": 1, "quality": 90, "mode": "sync", "webhook_url": "https://example.com/hooks/mintrender", "metadata": { "order": "1234", }, }, timeout=120,)body = res.json()if not res.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} ({body['error']['request_id']})")print(body)Request fields
| Field | Type | About |
|---|---|---|
template | string | Required. The template id (tpl_…) of your workspace. |
version | integer | A version number of the template. Default: the newest published version. Any version made by a publish can be used. |
data | object | Param name → value. A value is text, a number, true or false, a list of numbers or an object of layer fields. See Template data. |
ignore_unknown | boolean | Skip data keys that match no param. Without it, an unknown key is a 400 that names it. |
format | string | png (default), jpg, webp, svg or pdf. See Formats. |
sizes | list of string | Size names to make, each one file. Default ["main"]. 1 to 20 names, each at most 32 characters. |
scale | number | 0.1 to 4, default 1. Multiplies the pixel size of PNG, JPEG and WebP. SVG and PDF ignore it. |
quality | integer | 1 to 100, default 90. For JPEG and WebP only. |
mode | string | sync (default) or async. See Sync or job. |
webhook_url | string | An https or http URL that gets the render's event when it ends. At most 2,048 characters. See Jobs and webhooks. |
metadata | object | Your own values, given back with the render and in its webhook. At most 4 KB of JSON. |
The header Idempotency-Key makes a retry safe. See Idempotency.
The answer
A sync render answers 200:
{ "id": "rnd_9x7Lm2Qp8sT4vB0nC6kR", "status": "succeeded", "outputs": [ { "size": "main", "url": "https://files.mintrender.com/r/ws_…/a1b2….png", "width": 1200, "height": 630, "bytes": 84211, "format": "png" } ], "credits": 1, "cached": false, "took_ms": 212, "created": "2026-11-01T09:30:00.000Z"}| Field | About |
|---|---|
id | The render id (rnd_…). Use it with GET /v1/renders/{id}. |
status | succeeded for a finished sync render. See the statuses below. |
outputs | One item per size, in the order of sizes. |
outputs[].size | The size name. |
outputs[].url | The file on files.mintrender.com. Anyone with the URL can open it. See Files. |
outputs[].width, height | Pixels for PNG, JPEG and WebP, with scale. For SVG and PDF, the size in px without scale. |
outputs[].bytes | The file size. |
outputs[].format | png, jpg, webp, svg or pdf. |
outputs[].pages | PDF only: the number of pages. |
credits | What this render cost. Sizes that came from stored files cost 0. On a failed render it is the amount that was reserved and then given back. |
cached | true when every output came from a stored file. |
took_ms | Milliseconds the render took, or null while a job waits. |
error | Only on a failed job: code, message and sometimes field. |
metadata | Your metadata, when you sent it. |
created | When the render was asked for, in ISO 8601 UTC. |
The file in the answer
Add ?inline=1 to get the file itself in the answer. It works for a sync render with exactly one size. The answer has the file's Content-Type, Content-Disposition: inline; filename="render.png" (with the format's extension) and MR-Request-Id.
curl
curl -X POST "https://mintrender.com/v1/render?inline=1" \ -H "Authorization: Bearer $MINTRENDER_KEY" \ -H "Content-Type: application/json" \ -d '{"template":"tpl_3k2jQ8xLm0P4nB7vR2sT","data":{"title":"Ten ways to sell more"},"format":"png"}' \ -o og.pngJavaScript
import { writeFile } from "node:fs/promises"; const res = await fetch("https://mintrender.com/v1/render?inline=1", { method: "POST", headers: { Authorization: `Bearer ${process.env.MINTRENDER_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ template: "tpl_3k2jQ8xLm0P4nB7vR2sT", data: { title: "Ten ways to sell more", }, format: "png", }),});if (!res.ok) { const { error } = await res.json(); throw new Error(`${error.code}: ${error.message} (${error.request_id})`);}await writeFile("og.png", Buffer.from(await res.arrayBuffer()));Python
import os import requests res = requests.post( "https://mintrender.com/v1/render?inline=1", headers={"Authorization": f"Bearer {os.environ['MINTRENDER_KEY']}"}, json={ "template": "tpl_3k2jQ8xLm0P4nB7vR2sT", "data": { "title": "Ten ways to sell more", }, "format": "png", }, timeout=120,)if not res.ok: error = res.json()["error"] raise RuntimeError(f"{error['code']}: {error['message']} ({error['request_id']})")with open("og.png", "wb") as f: f.write(res.content)Sync or job
A sync render waits for the files. The API makes the request a job, and answers 202, when any of these is true:
modeisasync.- The format is PDF and the template has more than 10 pages.
- The request asks for more than 5 sizes.
curl
curl -X POST "https://mintrender.com/v1/render" \ -H "Authorization: Bearer $MINTRENDER_KEY" \ -H "Content-Type: application/json" \ -d '{"template":"tpl_3k2jQ8xLm0P4nB7vR2sT","data":{"name":"Ada Lovelace"},"format":"pdf","mode":"async"}'JavaScript
const res = await fetch("https://mintrender.com/v1/render", { method: "POST", headers: { Authorization: `Bearer ${process.env.MINTRENDER_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ template: "tpl_3k2jQ8xLm0P4nB7vR2sT", data: { name: "Ada Lovelace", }, format: "pdf", mode: "async", }),});const body = await res.json();if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);console.log(body);Python
import os import requests res = requests.post( "https://mintrender.com/v1/render", headers={"Authorization": f"Bearer {os.environ['MINTRENDER_KEY']}"}, json={ "template": "tpl_3k2jQ8xLm0P4nB7vR2sT", "data": { "name": "Ada Lovelace", }, "format": "pdf", "mode": "async", }, timeout=120,)body = res.json()if not res.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} ({body['error']['request_id']})")print(body){ "id": "rnd_9x7Lm2Qp8sT4vB0nC6kR", "status": "queued", "poll": "/v1/renders/rnd_9x7Lm2Qp8sT4vB0nC6kR"}Then poll GET /v1/renders/{id} or wait for the webhook. Jobs count toward the plan's running jobs (see Limits). ?inline=1 cannot be used with a job.
Get and cancel a render
curl
curl "https://mintrender.com/v1/renders/rnd_9x7Lm2Qp8sT4vB0nC6kR" \ -H "Authorization: Bearer $MINTRENDER_KEY"JavaScript
const res = await fetch("https://mintrender.com/v1/renders/rnd_9x7Lm2Qp8sT4vB0nC6kR", { method: "GET", headers: { Authorization: `Bearer ${process.env.MINTRENDER_KEY}`, },});const body = await res.json();if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);console.log(body);Python
import os import requests res = requests.get( "https://mintrender.com/v1/renders/rnd_9x7Lm2Qp8sT4vB0nC6kR", headers={"Authorization": f"Bearer {os.environ['MINTRENDER_KEY']}"}, timeout=120,)body = res.json()if not res.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} ({body['error']['request_id']})")print(body)| Status | Means |
|---|---|
queued | The job waits for a worker. |
rendering | The job is being drawn. |
succeeded | The files are ready in outputs. |
failed | The job failed. error says why. Its credits came back. |
canceled | You canceled the job before it started. Its credits came back. |
A job that is still queued can be canceled. The answer is the render with "status": "canceled". A job that has started or ended answers 409 not_queued.
curl
curl -X DELETE "https://mintrender.com/v1/renders/rnd_9x7Lm2Qp8sT4vB0nC6kR" \ -H "Authorization: Bearer $MINTRENDER_KEY"JavaScript
const res = await fetch("https://mintrender.com/v1/renders/rnd_9x7Lm2Qp8sT4vB0nC6kR", { method: "DELETE", headers: { Authorization: `Bearer ${process.env.MINTRENDER_KEY}`, },});const body = await res.json();if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);console.log(body);Python
import os import requests res = requests.delete( "https://mintrender.com/v1/renders/rnd_9x7Lm2Qp8sT4vB0nC6kR", headers={"Authorization": f"Bearer {os.environ['MINTRENDER_KEY']}"}, timeout=120,)body = res.json()if not res.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} ({body['error']['request_id']})")print(body)Free previews
POST /v1/preview gives a small image to check a template and data. It costs nothing and is never stored. The image is at most 600 px on its long side, has the "MintRender test" watermark, and is PNG, JPEG or WebP.
curl
curl -X POST "https://mintrender.com/v1/preview" \ -H "Authorization: Bearer $MINTRENDER_KEY" \ -H "Content-Type: application/json" \ -d '{"template":"tpl_3k2jQ8xLm0P4nB7vR2sT","data":{"title":"Draft title"},"size":"main","format":"png"}' \ -o preview.pngJavaScript
import { writeFile } from "node:fs/promises"; const res = await fetch("https://mintrender.com/v1/preview", { method: "POST", headers: { Authorization: `Bearer ${process.env.MINTRENDER_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ template: "tpl_3k2jQ8xLm0P4nB7vR2sT", data: { title: "Draft title", }, size: "main", format: "png", }),});if (!res.ok) { const { error } = await res.json(); throw new Error(`${error.code}: ${error.message} (${error.request_id})`);}await writeFile("preview.png", Buffer.from(await res.arrayBuffer()));Python
import os import requests res = requests.post( "https://mintrender.com/v1/preview", headers={"Authorization": f"Bearer {os.environ['MINTRENDER_KEY']}"}, json={ "template": "tpl_3k2jQ8xLm0P4nB7vR2sT", "data": { "title": "Draft title", }, "size": "main", "format": "png", }, timeout=120,)if not res.ok: error = res.json()["error"] raise RuntimeError(f"{error['code']}: {error['message']} ({error['request_id']})")with open("preview.png", "wb") as f: f.write(res.content)- With an API key, send
template, anddataandsizeif you want. It uses the published version. - Without a key, our own pages send a
scene. Such a preview draws no images from URLs or assets, needs a Turnstile token and allows 30 a minute per IP address. - Send either
templateorscene. A request with both, or with neither, is a 400.
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | A field has the wrong type or range, like scale 5 or 21 sizes. |
| 400 | invalid_param | A data value does not fit its layer, an unknown size, metadata over 4,096 characters of JSON, ?inline=1 with a job or with more than one size, or a webhook URL we do not call. |
| 400 | unknown_param | A data key matches no param, without ignore_unknown. |
| 400 | url_refused, url_failed, unsupported_type, image_too_large | An image URL in data or in the template cannot be used. field names it. |
| 400 | limit | The output is too large for one render. The message says which limit. |
| 401 | unauthorized | No key, or the key is not valid. |
| 402 | no_credits | Not enough credits for the sizes and pages that need a new file. |
| 404 | not_found | No such template, or no such version. |
| 409 | not_published | The template has no published version. |
| 409 | in_progress | A request with the same Idempotency-Key is still running. |
| 413 | too_large | The body is over 1 MB. |
| 422 | idempotency_mismatch | The Idempotency-Key was used with a different body. |
| 429 | rate_limited, busy | Too many calls, or too many running jobs. See Retry-After. |
| 451 | frozen, expired | The workspace is frozen, or a reviewer workspace has ended. |
| 500 | render_failed | The render failed. Try again. |
Seen MintRender used for content that breaks our terms? Report it. Questions about the API: support@mintrender.com, with the request id.