---
title: "Instagram carousel from a brief"
method: POST
path: "/v1/carousel"
tags: ["Carousel"]
---

# Instagram carousel from a brief

`POST /v1/carousel`

## Request

- Base URL: `https://apihub.co.il`
- URL: `https://apihub.co.il/v1/carousel`
- Auth: HTTP bearer

One call with a short brief returns a finished Instagram carousel: 5–10 slides, 1080×1080 (1:1, the feed's square — the default) or 1080×1350 (`aspect: "4:5"`, the taller post that takes more of the feed) JPEG, designed text (`language`: Hebrew, English, Ukrainian or Russian) over a photo, a generated picture or a brand-colour card — with the caption, at most 5 hashtags, alt text per slide, where every picture came from, and the plan as JSON. Asynchronous: the call answers 202 with the execution; poll GET /v1/executions/{id} for `output.slides[].url`. How it works: a planner writes the slides (one idea per slide; in the square a headline ≤ 6 words and 44 characters and a body ≤ 18 words and 120 characters, in 4:5 a headline ≤ 8 words and a body ≤ 25; slide 1 a hook, the last a call to action; facts, prices and dates only from your brief). Pictures: `own_images` first, in order; `images: "stock"` picks free photos for each slide in the slides' shape, checks them, and copies them with their licence (a slide without a fitting photo becomes a brand-colour card, never a paid generation); `ai` / `ai-pro` generate the cover first and the rest in its style; `none` makes brand-colour cards. Text is set by our render engine, never by the image model, so Hebrew, numbers, prices and @handles read correctly (Ukrainian and Russian are set in Rubik, the slide font with Cyrillic letters). Looks: `photo` (full-bleed picture, dark scrim, large headline), `card` (brand colour, big numbers, a picture on the cover only — the cheapest with `ai`), `split` (picture on top in a rounded frame, text panel below). The logo sits bottom-left for Hebrew (bottom-right for English) with the handle next to it, the slide marker ("2/7") and the swipe cue opposite. The plan carries its `aspect`, so a re-render keeps the shape. Fix one slide without paying for pictures again: edit `output.plan` and send it to POST /v1/carousel/render. The files are kept 24 hours — download them within that time. The price is the real cost of what ran (the planner, the photo check, each generated picture, the render) times the standard markup; the estimate is the worst case and is what is reserved.

**Billing:** Real cost of the run × the standard markup: the planner, the photo check or each generated picture, and the render per slide · **Rate limit:** 6/min · **Asynchronous**

## Headers

- `Idempotency-Key` string
- `X-Tag` string
- `x-wait-ms` integer

## Request body

- object
  - `cta` string: What the last slide asks for (follow, save, a link in the bio, a DM keyword)
  - `look` 'auto' | 'photo' | 'card' | 'split': photo = a full-bleed picture on every slide · card = brand colour, big numbers, a picture on the cover only · split = picture on top, text panel below. auto = photo with stock or your own pictures, card with generated ones or none
  - `plan` object: A plan returned by an earlier call, possibly edited: the planner is skipped
    - `look` 'photo' | 'card' | 'split', required
    - `brand` object, required
      - `font` 'Heebo' | 'Rubik' | 'Assistant' | 'Frank Ruhl Libre' | 'Secular One'
      - `accent` string, required
      - `handle` string
      - `primary` string, required
      - `logo_url` string, uri
    - `aspect` '1:1' | '4:5': The slides' shape: 1:1 (1080×1080) or 4:5 (1080×1350). A plan without it is 4:5 — every plan made before the square existed.
    - `slides` object[], required
      - `alt` string: Alt text of the finished slide
      - `body` string
      - `role` 'hook' | 'point' | 'step' | 'myth' | 'fact' | 'before' | 'after' | 'story' | 'proof' | 'recap' | 'cta', required
      - `image` object
        - `url` string, nullable
        - `model` string, nullable
        - `author` string, nullable
        - `source` 'stock' | 'ai' | 'own' | 'none', required
        - `licence` string, nullable
        - `page_url` string, nullable
        - `provider` string, nullable
        - `licence_url` string, nullable
      - `button` string: The call-to-action button (last slide)
      - `kicker` string: A small label above the headline (a pill)
      - `number` integer: The item's number (a tip, a step)
      - `focus_x` number: Which part of a picture wider than its window stays: 0 its left edge, 0.5 the middle (default), 1 its right edge
      - `focus_y` number: Which part of a picture taller than its window stays: 0 its top, 0.5 the middle, 1 its bottom. Default: the middle (in the square photo look the picture's middle is brought into the clear part above the text). Set it when the crop cuts the subject.
      - `headline` string, required
      - `image_url` string, uri: The picture this slide is rendered with. Change it to swap the picture; remove it for a brand-colour card.
      - `stock_query` string: English stock search for this slide's picture
      - `stock_short` string
      - `wants_image` boolean: Whether this slide carries its own picture
      - `image_prompt` string: English prompt for a generated picture (no text in it)
    - `caption` string
    - `palette` string
    - `version` 1
    - `hashtags` string[]
    - `language` 'he' | 'en' | 'uk' | 'ru', required
    - `archetype` 'tips' | 'myths' | 'steps' | 'before-after' | 'story' | 'product', required
    - `image_style` string
  - `tone` string
  - `brand` object
    - `font` 'Heebo' | 'Rubik' | 'Assistant' | 'Frank Ruhl Libre' | 'Secular One': Default Heebo; Rubik for Ukrainian and Russian (the only one of these fonts with Cyrillic letters: any other is replaced by it there)
    - `accent` string: The accent colour: numbers, pills, the button
    - `handle` string: The Instagram handle shown next to the logo, with or without @
    - `primary` string: The brand's main colour (#RRGGBB): the card and panel background
    - `logo_url` string, uri: PNG / JPEG / WebP logo, drawn bottom-left (Hebrew) or bottom-right (English)
  - `brief` string, required: The topic, or your own text to turn into slides. Facts, prices and dates are taken only from here.
  - `aspect` '1:1' | '4:5': The slides' shape: 1:1 = 1080×1080, the feed's square (default) · 4:5 = 1080×1350, the taller post that takes more of the feed. A square slide holds less text: headline ≤ 6 words, body ≤ 18 (4:5: 8 and 25). With `plan`: the plan's own shape unless you name one.
  - `images` 'stock' | 'ai' | 'ai-pro' | 'none': stock = free photos picked and checked for the slide · ai = generated (Gemini 3.1 Flash Image), the cover first and the rest in its style · ai-pro = Gemini 3 Pro Image · none = brand-colour cards
  - `slides` integer
  - `audience` string
  - `brand_id` string: A brand of yours (GET /v1/brands): its colours, logo, handle, font and tone fill what `brand` and `tone` leave out — `default` = your default brand
  - `language` 'he' | 'en' | 'uk' | 'ru': he (default) · en · uk · ru — the language the slides, the caption and the hashtags are written in. Ukrainian and Russian are set in Rubik.
  - `archetype` 'auto' | 'tips' | 'myths' | 'steps' | 'before-after' | 'story' | 'product': auto = the planner picks the structure that fits the brief
  - `own_images` string[]: Your own pictures (JPEG / PNG / WebP), used first, in order

## Response `200`

Completed (or 202 while processing)

- object
  - `id` string: Execution id — stable across internal retries; use it with GET /v1/executions/{id}
  - `status` 'pending' | 'processing' | 'completed' | 'partial' | 'failed' | 'cancelled' | 'timeout'
  - `operation` string
  - `tag` string, nullable: Your X-Tag, if sent
  - `created_at` string, date-time
  - `completed_at` string, date-time, nullable
  - `output` object: Operation result (see each operation). While a Studio render is processing: `stage` (queued · transcribing · downloading · preparing · cleaning_voice · mixing_audio · rendering · finishing), `phase` (the engine's: download · prepare · voice · audio · encode · qa · upload, or null), `progress` (a percent, 0–100) and `progress_fraction` (0–1), `eta_seconds` (seconds left, never below 0) and `estimate_seconds`
  - `error` string, nullable
  - `usage` object
    - `credits_charged` number, nullable
    - `credits_reserved` number
    - `estimated_credits` number
    - `cost_source` 'provider_reported' | 'calculated' | 'estimated' | 'null', nullable
    - `provider_usage` object, nullable: Normalised consumption, e.g. { VIDEO_SECOND: 22.4 }
  - `balance` object
    - `available` number
    - `reserved` number

## Other responses

- `202`: Processing — poll GET /v1/executions/{id}
- `400`: Invalid input
- `402`: Insufficient credits / project budget
- `404`: Resource not found (or not yours)
- `429`: Rate limited
- `503`: Service temporarily paused

---

[API](https://skmtc.dev/apihub/apis/apihub-one-api-for-chat-image-video-ai.md) · [All operations](https://skmtc.dev/apihub/apis/apihub-one-api-for-chat-image-video-ai/llms.txt) · [OpenAPI document](https://skmtc.dev/apihub/apis/apihub-one-api-for-chat-image-video-ai/revisions/a8caa42835ce?raw)
