Generate a template with AI
Describe a design in plain words and get a finished, saved template back from one API call, ready to render. Body fields, waiting, polling, costs and limits.
POST /v1/designs/generate asks the design agent to make a new template from a description. It lays out the
pages, writes sample text, uses your brand colors, fonts and logo, and gives keys to the layers a render will
change, such as title, photo or cta. The template is saved in your workspace, so you can render it right
away. To do the same in the app, see Design with AI.
Send a request
curl https://api.jamframe.com/v1/designs/generate \
-H "Authorization: Bearer $JAMFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Announce our summer sale, 30% off everything this weekend. Bold and playful.",
"size": "instagram-portrait"
}'const res = await fetch("https://api.jamframe.com/v1/designs/generate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JAMFRAME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: "Announce our summer sale, 30% off everything this weekend. Bold and playful.",
size: "instagram-portrait",
}),
});
const { data } = await res.json();
// 201: data.design is ready. 202: poll data.jobId.
console.log(res.status, data);import os
import requests
res = requests.post(
"https://api.jamframe.com/v1/designs/generate",
headers={"Authorization": f"Bearer {os.environ['JAMFRAME_API_KEY']}"},
json={
"prompt": "Announce our summer sale, 30% off everything this weekend. Bold and playful.",
"size": "instagram-portrait",
},
)
data = res.json()["data"]
# 201: data["design"] is ready. 202: poll data["jobId"].
print(res.status_code, data)Body fields
| Field | Required | What it does |
|---|---|---|
prompt |
Yes | What to design, in plain words. Up to 4,000 characters. |
size |
No | The canvas size: a preset id such as instagram-post, instagram-story or linkedin-carousel, a short name (square, portrait, story, landscape, widescreen, thumbnail, pin), or "WIDTHxHEIGHT" such as "1080x1350". Default: 1080 × 1350. |
width, height |
No | Instead of size: whole numbers from 16 to 10,000 pixels. |
pages |
No | How many pages, from 1 to 10. More than 1 makes a carousel. Default: 1. |
brand |
No | Which brand kit to use. See below. Default: your workspace’s brand library. |
referenceImageUrl |
No | The https link of one image for the agent to look at or use: PNG, JPEG, GIF or WebP, up to 10 MB. |
name |
No | The template’s name, up to 120 characters. Default: a name made from the prompt. |
async |
No | true answers 202 at once, without waiting. Default: false. |
Any other field is a 400 VALIDATION_ERROR.
If the design uses your reference image, JamFrame saves a copy in your brand library.
The brand kit
brand value |
What the agent uses |
|---|---|
Left out, or true |
The colors, fonts and logo in your workspace’s brand library. |
false |
No brand kit. |
| An object | The colors, fonts and logo you send. Your brand library’s other images can still be used. |
The object takes three fields, each optional:
colors: up to 12 hex colors, such as"#0b1f4d". The first is the main color.fonts: up to 4 font names. Each must be a font in your brand library or a Google font the editor offers.logo: the link of an image in your brand library, or anyhttpsimage link.
{
"prompt": "Five tips for running better remote meetings",
"size": "linkedin-carousel",
"pages": 5,
"brand": { "colors": ["#0b1f4d", "#ff6b5b"], "fonts": ["Poppins", "Inter"] }
}
The answer
A design usually takes 10 to 40 seconds. The request waits up to about 25 seconds:
201 Created: the template is ready.data.designholds it.202 Accepted: the agent is still working. The answer is{"data": {"jobId": "…", "status": "running"}}, and theLocationheader has the link to poll. With"async": trueyou always get this answer.
A 201 answer looks like this (some fields left out):
{
"data": {
"jobId": "cmw9uoeb9000binw52g648888",
"status": "succeeded",
"message": "A bold coral and navy sale post with a photo slot, the headline, a subline and a \"Shop now\" button.",
"changes": ["Page 1: background #fff7ed", "Added image \"Photo\"", "Added text \"Headline\""],
"creditsCharged": 5,
"createdAt": "2026-11-14T09:12:44.000Z",
"finishedAt": "2026-11-14T09:13:02.000Z",
"design": {
"id": "clx8m2k3p0002abcd1234efgh",
"name": "Announce our summer sale, 30% off everything",
"orientation": "portrait",
"pages": 1,
"parameters": [
{ "key": "photo", "type": "image", "pages": [1], "example": "" },
{ "key": "title", "type": "text", "pages": [1], "example": "Summer sale" }
],
"sizes": [],
"editorUrl": "https://app.jamframe.com/designs/clx8m2k3p0002abcd1234efgh/edit"
}
}
}
design has the same fields as GET /v1/designs/{id}, plus editorUrl, a
link that opens the template in the editor.
Poll a running job
Call GET /v1/designs/generate/{jobId} every few seconds until status is no longer running:
status |
Meaning |
|---|---|
running |
The agent is still working. |
succeeded |
Done. The answer has design, as in the 201 answer. |
failed |
It failed. error holds the error code and message. No AI credits were charged. |
async function waitForDesign(jobId) {
for (;;) {
const res = await fetch(`https://api.jamframe.com/v1/designs/generate/${jobId}`, {
headers: { Authorization: `Bearer ${process.env.JAMFRAME_API_KEY}` },
});
const { data } = await res.json();
if (data.status === "succeeded") return data.design;
if (data.status === "failed") throw new Error(`${data.error.error}: ${data.error.message}`);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
}
A job that is still running 15 minutes after it started is marked failed with AI_TIMEOUT. A job id from
another workspace answers 404 NOT_FOUND.
Use the new template
The new template shows on the Templates page like any other, and fires a design.created
webhook event. Render it at once with its id as templateId. The keys in
parameters are the values you can send in modifications:
{
"templateId": "clx8m2k3p0002abcd1234efgh",
"modifications": { "title": "Summer sale", "photo": "https://example.com/beach.jpg" }
}
See Render images and PDFs. Open editorUrl to change the design by hand.
Cost and limits
- Each design costs 5 AI credits, from the same AI credits as the other AI features. A failed design is refunded. See Credits.
- Each workspace can generate 30 designs per 10 minutes. More answer
429 RATE_LIMITED. This counts on top of your per-minute limit.
Errors
Errors that happen while the request waits come back as the answer. Errors after a 202 show in the job’s
error.
| Status | error |
What to do |
|---|---|---|
400 |
VALIDATION_ERROR |
A field is wrong or unknown, or the reference image couldn’t be used. Read message. |
402 |
AI_CREDITS_EXCEEDED |
Your AI credits for this period are used up. |
422 |
AI_CONTENT_REFUSED |
The AI provider refused the prompt under its content policy. Change the prompt. |
429 |
RATE_LIMITED |
Over 30 designs in 10 minutes, or over your per-minute limit. Wait for Retry-After seconds. |
502 |
AI_BAD_OUTPUT or AI_UPSTREAM_ERROR |
The AI didn’t make a usable design, or the provider failed. Try again, maybe with a clearer prompt. |
503 |
AI_UNAVAILABLE |
AI is off right now. Try later. |
No AI credits are charged when a design fails. See Errors for the full list.
Related
- Design with AI in the app: Design with AI
- Find templates and their keys: List templates and renders
- Hear about new templates: Webhooks
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.