Skip to content
JamFrame
Docs menu

Render images and PDFs

Use POST /v1/render to turn a template into PNG, JPEG or WebP images, a carousel or a PDF, and choose the format, pages, quality and how long the links work.

POST /v1/render makes files from a template. You send the template id and the values to fill in. The answer comes when the files are ready, with a link to each one.

The request

POST https://api.jamframe.com/v1/render
Field Type What it does
templateId string, required The template to render: the id of a template in your workspace, the id of a library template, or a library template’s slug.
modifications object The values to fill in, by key. See Modification keys.
response object The format and other output options, below.
expiresIn integer How long the file links work, in seconds: from 1 to 604800 (7 days). The default is 300 (5 minutes).
async boolean true answers at once and renders in the background. See Async renders.
webhookUrl string Only with async: a URL to call when the render ends. See Async renders.

Every field of response is optional:

Field Values Default What it does
format png, jpeg (or jpg), webp, pdf png The file type.
type url, base64 url url stores the files and answers with links. base64 puts the file’s bytes in the answer and stores nothing.
scale 1, 2 or 3 1 Pixel density for images. 2 makes an image twice the template’s width and height. PDFs ignore it.
quality 1 to 100 Compression for jpeg and webp. Lower is smaller and blurrier. PNG and PDF ignore it.
includePages page numbers, such as [1, 3] all pages The pages to render. Pages start at 1.
size a size, such as story the template’s size Render at another size. See Smart Resize.
sizes 1 to 10 sizes Render several sizes in one call. See Smart Resize.

To fill layers with AI or translate the text while rendering, see AI at render time.

Render one image

This request changes three layers and the color of one of them, and makes a PNG at twice the size:

curl https://api.jamframe.com/v1/render \
  -H "Authorization: Bearer $JAMFRAME_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "clx8m2k3p0001abcd1234efgh",
    "modifications": {
      "title": "Summer sale ends Sunday",
      "subline": "Up to 50% off everything",
      "photo": "https://example.com/images/beach.jpg",
      "title.color": "#FF4D4F"
    },
    "response": { "format": "png", "scale": 2 }
  }'
const res = await fetch("https://api.jamframe.com/v1/render", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.JAMFRAME_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    templateId: "clx8m2k3p0001abcd1234efgh",
    modifications: {
      title: "Summer sale ends Sunday",
      subline: "Up to 50% off everything",
      photo: "https://example.com/images/beach.jpg",
      "title.color": "#FF4D4F",
    },
    response: { format: "png", scale: 2 },
  }),
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error}: ${body.message}`);
console.log(body.data.content); // the link to the image
import os
import requests

res = requests.post(
    "https://api.jamframe.com/v1/render",
    headers={"Authorization": f"Bearer {os.environ['JAMFRAME_API_KEY']}"},
    json={
        "templateId": "clx8m2k3p0001abcd1234efgh",
        "modifications": {
            "title": "Summer sale ends Sunday",
            "subline": "Up to 50% off everything",
            "photo": "https://example.com/images/beach.jpg",
            "title.color": "#FF4D4F",
        },
        "response": {"format": "png", "scale": 2},
    },
)
res.raise_for_status()
print(res.json()["data"]["content"])  # the link to the image

The answer:

{
  "data": {
    "content": "https://cdn.jamframe.com/renders/ws_abc123/5f0c6b0e.png?Expires=…",
    "expiresAt": "2026-11-14T12:05:00.000Z",
    "type": "url",
    "format": "png",
    "pages": [1],
    "credits": 1,
    "renderId": "clx8n0q1r0004abcd1234efgh",
    "responseTime": 1840
  }
}

The answer

Field What it holds
data.content A link to each file, or its base64 bytes. One file gives a string. Several images give an array, in page order.
data.expiresAt When the links stop working. Only for url renders.
data.type url or base64.
data.format png, jpeg, webp or pdf. A jpg request says jpeg.
data.pages The template pages that were rendered.
data.credits The render credits this render cost.
data.renderId The render’s id. Use it to find the render later, or when you contact support.
data.responseTime How long the server took to render, in milliseconds.
warnings Next to data, only when something didn’t stop the render but might be a mistake.

The most common warning is a key that matched nothing in the template, often a typo:

{
  "data": { "content": "https://cdn.jamframe.com/renders/…", "credits": 1 },
  "warnings": ["Unknown modification keys (no matching layer or {{placeholder}}): titel"]
}

Carousels: several pages

A template with several pages makes one image per page. content is then an array of links, in page order, and each image costs one credit. To give each page its own text, put pageN@ in front of a key:

{
  "templateId": "clx8m2k3p0002abcd1234efgh",
  "modifications": {
    "brand": "Acme Coffee",
    "title": "5 ways to brew better coffee",
    "page2@title": "1. Grind just before brewing",
    "page3@title": "2. Weigh your beans",
    "[email protected]": 56
  },
  "response": { "format": "jpeg", "quality": 90 }
}
{
  "data": {
    "content": [
      "https://cdn.jamframe.com/renders/ws_abc123/9a1e2b3c-p1.jpg?Expires=…",
      "https://cdn.jamframe.com/renders/ws_abc123/9a1e2b3c-p2.jpg?Expires=…",
      "https://cdn.jamframe.com/renders/ws_abc123/9a1e2b3c-p3.jpg?Expires=…"
    ],
    "expiresAt": "2026-11-14T12:05:00.000Z",
    "type": "url",
    "format": "jpeg",
    "pages": [1, 2, 3],
    "credits": 3,
    "renderId": "clx8n0q1r0005abcd1234efgh",
    "responseTime": 4210
  }
}
  • A render can include at most 20 pages.
  • includePages picks pages, such as [2, 3]. The files always come out in the template’s page order.
  • A page number the template doesn’t have is a 400.

To design carousels, see Pages and carousels.

PDFs

"format": "pdf" makes one PDF with every rendered page, in order. content is always one link, and each page costs one credit. This request makes a PDF of pages 1 and 2, with a link that works for an hour:

{
  "templateId": "clx8m2k3p0003abcd1234efgh",
  "modifications": { "customer_name": "Jane Doe" },
  "response": { "format": "pdf", "includePages": [1, 2] },
  "expiresIn": 3600
}

Each PDF page has the template’s size. A template made from the A4 or US Letter size prints on that paper size. scale and quality don’t apply to PDFs.

With "type": "base64", content holds each file’s bytes as base64, with no data: prefix. Nothing is stored, so there are no links and no expiresAt. Decode the string and save it.

  • Don’t send expiresIn with base64. That’s a 400.
  • Base64 works only for renders that wait for the answer, not with async or sizes.
  • Big files make big answers. For large PDFs and carousels, use links.
  • Links work for expiresIn seconds: 5 minutes by default, at most 7 days. expiresAt says until when. After that, a link answers 403.
  • Files of an API render are deleted 8 days after the render, so a link never outlives its file. See How long files last.
  • To keep a file, download it and store it yourself.
  • GET /v1/renders/{id} gives fresh links to a past render’s files, for as long as the files exist. See Templates and renders.

Images in a render

Image URLs, in the template or in modifications, must be public http:// or https:// addresses. data: URLs work too.

  • An address on a private network, such as localhost or 10.0.0.5, is refused with 400 before anything renders.
  • If an image can’t be loaded, the render fails with 422 and the message names the URL. This happens when the image isn’t public, isn’t an image, its server doesn’t answer in time, or its name leads to a private address. No credits are charged.

Limits

Limit Value
Pages per render 20
Output size At most 36 megapixels per page: width × height × scale². A 1080 × 1350 page at scale 3 is 13.1 megapixels. Larger is a 400.
Time Up to 60 seconds per page, and 100 seconds for the whole render. A render that runs out of time fails with 500 and costs nothing.
Keys Up to 200 characters each: the whole key, with pageN@ and .property. Text values up to 20,000 characters.
Request body 5 MB

Credits

  • One credit per image, or per PDF page. A single image costs 1, a 5-page carousel 5, a 3-page PDF 3. With sizes, it’s 1 per page per size.
  • Library templates cost the same as your own.
  • Credits are charged only when the render works. A failed render is free.
  • If your organization can’t pay for a render, it’s refused before it starts, with 402 QUOTA_EXCEEDED. If an admin has capped this workspace’s share of credits, it’s 402 WORKSPACE_QUOTA_EXCEEDED.

See Credits.

Common errors

Status error What it means
400 VALIDATION_ERROR Something in the request is wrong: a missing field, a page or size the template doesn’t have, or a style property a layer doesn’t take. message says what. When the body has the wrong shape, detail.issues lists the fields.
402 QUOTA_EXCEEDED Not enough credits left.
404 NOT_FOUND No template with that id in this workspace or the library.
422 VALIDATION_ERROR An image couldn’t be loaded. Nothing was charged.
500 INTERNAL The render failed or took too long. Nothing was charged, so you can try again.

See Errors for every code.

Last updated

Still stuck?

Email [email protected] and a real person will reply. Tell us the template you were using and what you expected to happen.