MintRender
All docs pages

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

FieldTypeAbout
templatestringRequired. The template id (tpl_…) of your workspace.
versionintegerA version number of the template. Default: the newest published version. Any version made by a publish can be used.
dataobjectParam 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_unknownbooleanSkip data keys that match no param. Without it, an unknown key is a 400 that names it.
formatstringpng (default), jpg, webp, svg or pdf. See Formats.
sizeslist of stringSize names to make, each one file. Default ["main"]. 1 to 20 names, each at most 32 characters.
scalenumber0.1 to 4, default 1. Multiplies the pixel size of PNG, JPEG and WebP. SVG and PDF ignore it.
qualityinteger1 to 100, default 90. For JPEG and WebP only.
modestringsync (default) or async. See Sync or job.
webhook_urlstringAn https or http URL that gets the render's event when it ends. At most 2,048 characters. See Jobs and webhooks.
metadataobjectYour 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"}
FieldAbout
idThe render id (rnd_…). Use it with GET /v1/renders/{id}.
statussucceeded for a finished sync render. See the statuses below.
outputsOne item per size, in the order of sizes.
outputs[].sizeThe size name.
outputs[].urlThe file on files.mintrender.com. Anyone with the URL can open it. See Files.
outputs[].width, heightPixels for PNG, JPEG and WebP, with scale. For SVG and PDF, the size in px without scale.
outputs[].bytesThe file size.
outputs[].formatpng, jpg, webp, svg or pdf.
outputs[].pagesPDF only: the number of pages.
creditsWhat 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.
cachedtrue when every output came from a stored file.
took_msMilliseconds the render took, or null while a job waits.
errorOnly on a failed job: code, message and sometimes field.
metadataYour metadata, when you sent it.
createdWhen 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.png

JavaScript

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:

  • mode is async.
  • 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)
StatusMeans
queuedThe job waits for a worker.
renderingThe job is being drawn.
succeededThe files are ready in outputs.
failedThe job failed. error says why. Its credits came back.
canceledYou 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.png

JavaScript

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, and data and size if 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 template or scene. A request with both, or with neither, is a 400.

Errors

StatusCodeWhen
400invalid_requestA field has the wrong type or range, like scale 5 or 21 sizes.
400invalid_paramA 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.
400unknown_paramA data key matches no param, without ignore_unknown.
400url_refused, url_failed, unsupported_type, image_too_largeAn image URL in data or in the template cannot be used. field names it.
400limitThe output is too large for one render. The message says which limit.
401unauthorizedNo key, or the key is not valid.
402no_creditsNot enough credits for the sizes and pages that need a new file.
404not_foundNo such template, or no such version.
409not_publishedThe template has no published version.
409in_progressA request with the same Idempotency-Key is still running.
413too_largeThe body is over 1 MB.
422idempotency_mismatchThe Idempotency-Key was used with a different body.
429rate_limited, busyToo many calls, or too many running jobs. See Retry-After.
451frozen, expiredThe workspace is frozen, or a reviewer workspace has ended.
500render_failedThe 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.