User-created templates
Create a template once, then fill it with new copy for every post — at a fraction of the cost.
This is the workhorse pattern for content products: during onboarding, create a template the user loves; from then on, every post is a near-free fill of that template with new copy. It's how a social scheduling tool, a franchise marketing platform, or a "posts from your content plan" feature stays both on-brand and cheap.
1. Create template candidates
Feed the user's brand kit and everything you know about them, and ask for options:
const create = (body: object) =>
fetch("https://api.rivermark.ai/design", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIVERMARK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}).then((r) => r.json());
const candidates = await create({
task: [
"Create 4 candidate Instagram post templates for this brand.",
"Option 1 is photo-led, option 2 is typographic, option 3 is product-led,",
"and option 4 is a bold color-block design.",
"Each should work with varied copy lengths.",
].join(" "),
references: [{ type: "design", id: brandkit.design_id }],
creativity: 2, // explore widely for templates
output: [
{ id: "option_1", size: "1080x1350" },
{ id: "option_2", size: "1080x1350" },
{ id: "option_3", size: "1080x1350" },
{ id: "option_4", size: "1080x1350" },
],
});Present the four rendered options to the user as option 1–4.
2. Let them pick — or tweak
If they pick one, keep only that option and save it as their template:
// The design holds all four frames; continuing from it edits all of them.
const OPTIONS = ["photo-led", "typographic", "product-led", "color-block"];
const picked = 2; // the user picked option 2
const template = await create({
task: `Remove everything from the design except option ${picked} (the ${OPTIONS[picked - 1]} template). This is now our base template.`,
continue_from: candidates.design_id,
});
await db.templates.create({ userId: user.id, designId: template.design_id });If they want changes first — "I love #1, but calmer colors" — run an editing turn with continue_from (see Editing) before saving. A quick "edit" button that opens a chatbox over the candidate is minimal integration work and dramatically improves template quality — the AI design editor guide covers that loop in full.
3. Fill the template for every post
When the user wants a visual for a post, pass the template as the reference with the new copy in the task:
const post = await create({
task: [
"Create a post from this template with the following copy:",
"'The Autumn Roast is here — notes of fig, cedar, and bonfire smoke.'",
].join(" "),
references: [{ type: "design", id: template.designId }],
creativity: 0, // mechanical fill: exact, predictable, cheap
image_source: "none", // no new imagery unless the template asks for it
output: [{ id: "post", size: "1080x1350" }],
});Offer the user a simple "edit" button on the filled post, too — same chatbox pattern, continue_from the filled post's ID. When they're happy, schedule the rendered images.
Why agentic filling beats variable substitution
A template is HTML/CSS, not a rigid schema with {{headline}} slots. When the copy is longer than the last post's, the agent reflows — shrinks type, tightens leading, rebalances the composition — instead of clipping or overflowing. Short copy gets room to breathe. This is exactly the failure mode that makes classic template systems brittle in production, and auto-review catches the rest.
Economics
Filling a previously approved template with provided copy is the cheapest meaningful operation in the API — see Costs. At that price, "generate a visual for every post" stops being a premium feature and becomes a default.

