Skip to content

Automate Instagram carousels with the Image API

A carousel is a set of images posted together. With the Image API you render each slide from the same template, with that slide’s text, and let your scheduling tool publish the set. There is no “carousel” endpoint and none is needed: one call per slide, a short loop, and the images land in a folder ready to upload.

This guide gives you the complete loop as a runnable Python script (standard library only), and a neutral hand-off to the publishing step.

  • A TemplateFox API key (sk_…) from the dashboard — the Image API reference covers every request field.

  • An image template whose text blocks are named layers. The script below assumes these names — rename them to match your own template:

    Layer nameWhat goes in it
    kickerthe small label above the title (e.g. 1 · Rent)
    titlethe headline of the slide
    bodyone or two sentences
    counterthe slide position, filled in by the script (2 / 6)
    brandyour handle, filled in by the script (@maltaexpat)

    Size it for the feed: 1080×1350 (portrait, the format Instagram shows largest) or 1080×1080 (square). The output keeps the template’s aspect ratio; width in the request only scales it.

You can build the template in the editor (name each layer in the Layers panel), or let the authoring agent write it from a description:

Create a slide template with the authoring agent
curl -X POST https://api.templatefox.com/v1/agent/templates \
-H "x-api-key: sk_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "carousel-slide",
"width": 1080,
"height": 1350,
"instructions": "An Instagram carousel slide. Named text layers: kicker (small uppercase label), title (large headline, up to 5 lines), body (paragraph), counter (slide position, bottom right), brand (handle, bottom left). Off-white background, dark navy text, one indigo accent."
}'

The job is asynchronous: poll GET /v1/agent/jobs/{job_id} until it is completed; the response carries the template_id and a preview_url. Check the layer names with GET /v1/templates/{template_id}/layers.

One file per carousel. Each entry is a slide; each key is a layer name of your template and each value is the text that goes into that layer.

slides.json
{
"template_id": "your-template-id",
"brand": "@maltaexpat",
"format": "png",
"slides": [
{
"kicker": "Moving to Malta",
"title": "5 things nobody tells you before moving to Malta",
"body": "Rent, residency, the bus, the sea and the one thing every expat wishes they had known. Swipe."
},
{
"kicker": "1 · Rent",
"title": "Rent is the first shock",
"body": "Sliema and St Julian's cost the most. Gzira, Msida and Birkirkara are ten minutes away, for a lot less."
},
{
"kicker": "2 · Residency",
"title": "Register before the three-month mark",
"body": "No visa for EU citizens, but a residence card. Bring your lease, your ID and proof of income."
},
{
"kicker": "3 · Getting around",
"title": "Nobody is on time. Not even the bus.",
"body": "Buses are cheap and go everywhere, late. If your commute matters, live near work or get a scooter."
},
{
"kicker": "4 · The sea",
"title": "The sea is the real salary",
"body": "Swimming season runs from May to November, ten minutes from any office in the country. That's the deal."
},
{
"kicker": "5 · The magic word",
"title": "Learn the word “mela”",
"body": "It means everything and nothing. Drop it at the right moment and you're a local."
}
]
}

Where this JSON comes from is up to you: a blog post split into slides, one row of a spreadsheet, the output of an AI assistant, a database query. The script does not care.

Save this as render_carousel.py. It reads the JSON, makes one POST /v1/image/create call per slide with the slide’s text as modifications, and writes slide-01.png, slide-02.png, … into the output folder. export_type: "binary" returns the image bytes directly, so there is no second download.

render_carousel.py
#!/usr/bin/env python3
"""Render every slide of an Instagram carousel with the TemplateFox Image API.
Usage:
TEMPLATEFOX_API_KEY=sk_... python3 render_carousel.py slides.json ./out
Each key of a slide is a layer name of your template; the value is the text to
put in that layer. The script adds the "counter" layer ("1 / 6") and the
"brand" layer for you. No dependency beyond the standard library.
"""
import json
import os
import sys
import urllib.request
from pathlib import Path
API_URL = "https://api.templatefox.com/v1/image/create"
API_KEY = os.environ["TEMPLATEFOX_API_KEY"]
def render_slide(template_id: str, modifications: list[dict], fmt: str) -> bytes:
"""One API call = one slide. export_type=binary returns the image bytes directly."""
payload = {
"template_id": template_id,
"modifications": modifications,
"format": fmt,
"export_type": "binary",
}
request = urllib.request.Request(
API_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request) as response:
return response.read()
def main(spec_path: str, out_dir: str) -> None:
spec = json.loads(Path(spec_path).read_text(encoding="utf-8"))
slides = spec["slides"]
total = len(slides)
fmt = spec.get("format", "png")
out = Path(out_dir)
out.mkdir(parents=True, exist_ok=True)
for index, slide in enumerate(slides, start=1):
modifications = [
{"name": "counter", "text": f"{index} / {total}"},
{"name": "brand", "text": spec["brand"]},
]
modifications += [{"name": layer, "text": text} for layer, text in slide.items()]
image = render_slide(spec["template_id"], modifications, fmt)
path = out / f"slide-{index:02d}.{fmt}"
path.write_bytes(image)
print(f"{path} {len(image) // 1024} KB")
print(f"{total} slides rendered in {out}/ — ready to upload to your scheduler.")
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit("usage: render_carousel.py slides.json out_dir")
main(sys.argv[1], sys.argv[2])

Run it:

Terminal window
TEMPLATEFOX_API_KEY=sk_your_api_key python3 render_carousel.py slides.json ./out
Output
out/slide-01.png 39 KB
out/slide-02.png 27 KB
out/slide-03.png 32 KB
out/slide-04.png 34 KB
out/slide-05.png 29 KB
out/slide-06.png 25 KB
6 slides rendered in out/ — ready to upload to your scheduler.

Each call costs one credit, so a six-slide carousel costs six credits. If a layer name in your JSON does not exist in the template, the image still renders and the API lists the unknown name in the response’s warnings (with export_type: "url"); fix the name in either the JSON or the template.

Prefer a typed client? The same call with the official SDKs:

Node.js — @templatefox/sdk
import { Configuration, ImageApi } from "@templatefox/sdk";
const api = new ImageApi(new Configuration({ apiKey: "sk_your_api_key" }));
const slide = await api.createImage({
templateId: "your-template-id",
format: "png",
modifications: [
{ name: "kicker", text: "1 · Rent" },
{ name: "title", text: "Rent is the first shock" },
{ name: "counter", text: "2 / 6" },
],
});
console.log(slide.url);
Python — templatefox
from templatefox import ApiClient, Configuration, ImageApi, CreateImageRequest, Modification
config = Configuration()
config.api_key["ApiKeyAuth"] = "sk_your_api_key"
with ApiClient(config) as client:
slide = ImageApi(client).create_image(CreateImageRequest(
template_id="your-template-id",
format="png",
modifications=[
Modification(name="kicker", text="1 · Rent"),
Modification(name="title", text="Rent is the first shock"),
Modification(name="counter", text="2 / 6"),
],
))
print(slide.url)

Open the folder. The things worth checking on a new template, before you automate the trigger:

  • Long titles. Text is clipped at the edge of its box; there is no automatic shrinking. If a title gets cut, shorten it in the JSON or enlarge the box in the template.
  • The counter and the brand are on every slide, and the last slide reads 6 / 6.
  • Format. png for crisp text; jpeg or webp (smaller files) if your scheduler prefers them — set "format" in the JSON.

TemplateFox stops at the images. Publishing a carousel means giving those images, in order, to a tool that posts to Instagram on a date — any scheduler that accepts several images per post works. Two ways to do the hand-off:

  • Upload the folder in the tool’s media library and compose the carousel from there — the simplest start, no code.
  • Call the tool’s API: ask for the image with export_type: "url" instead of binary (the response then carries a signed url valid for expiration seconds, up to seven days), and pass the six URLs to the scheduler’s “create post” endpoint in slide order.

For example, Nadia takes a list of image URLs and a date and posts the carousel at that time — that is how our own accounts run. Other schedulers expose the same shape; check the slide limit each one applies before you send a long carousel.

Once the loop works by hand, hang it on a trigger so it runs without you:

  • a cron job that picks the next row of a spreadsheet every morning,
  • an automation in n8n, Make or Zapier that fires when a new article is published and builds the JSON from it,
  • a webhook from your CMS.

The script takes the JSON path as its first argument, so each trigger only has to write one file and run one command.