RivermarkRivermark
Guides

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:

onboarding.ts
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:

TypeScript
// 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:

post.ts
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.

On this page