Generate Slideshow
curl --request POST \
--url https://api.example.com/api/partner/v1/slideshows/generate \
--header 'Content-Type: application/json' \
--data '
{
"prompt": "<string>",
"product_id": {},
"pack_id": {},
"image_sourcing": "<string>",
"reference": {},
"slide_count": 123,
"slideshow_type": "<string>",
"aspect_ratio": "<string>",
"language": "<string>",
"advanced_settings": {
"font_size": {},
"text_preset": "<string>",
"text_width": "<string>"
},
"skip_ai": true,
"slide_config": {
"total_slides": 123,
"slide_types": [
{}
],
"custom_images": {},
"pinned_images": {},
"slide_texts": {},
"slide_text_elements": {},
"pack_assignments": {}
}
}
'import requests
url = "https://api.example.com/api/partner/v1/slideshows/generate"
payload = {
"prompt": "<string>",
"product_id": {},
"pack_id": {},
"image_sourcing": "<string>",
"reference": {},
"slide_count": 123,
"slideshow_type": "<string>",
"aspect_ratio": "<string>",
"language": "<string>",
"advanced_settings": {
"font_size": {},
"text_preset": "<string>",
"text_width": "<string>"
},
"skip_ai": True,
"slide_config": {
"total_slides": 123,
"slide_types": [{}],
"custom_images": {},
"pinned_images": {},
"slide_texts": {},
"slide_text_elements": {},
"pack_assignments": {}
}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
prompt: '<string>',
product_id: {},
pack_id: {},
image_sourcing: '<string>',
reference: {},
slide_count: 123,
slideshow_type: '<string>',
aspect_ratio: '<string>',
language: '<string>',
advanced_settings: {font_size: {}, text_preset: '<string>', text_width: '<string>'},
skip_ai: true,
slide_config: {
total_slides: 123,
slide_types: [{}],
custom_images: {},
pinned_images: {},
slide_texts: {},
slide_text_elements: {},
pack_assignments: {}
}
})
};
fetch('https://api.example.com/api/partner/v1/slideshows/generate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/partner/v1/slideshows/generate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'prompt' => '<string>',
'product_id' => [
],
'pack_id' => [
],
'image_sourcing' => '<string>',
'reference' => [
],
'slide_count' => 123,
'slideshow_type' => '<string>',
'aspect_ratio' => '<string>',
'language' => '<string>',
'advanced_settings' => [
'font_size' => [
],
'text_preset' => '<string>',
'text_width' => '<string>'
],
'skip_ai' => true,
'slide_config' => [
'total_slides' => 123,
'slide_types' => [
[
]
],
'custom_images' => [
],
'pinned_images' => [
],
'slide_texts' => [
],
'slide_text_elements' => [
],
'pack_assignments' => [
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/partner/v1/slideshows/generate"
payload := strings.NewReader("{\n \"prompt\": \"<string>\",\n \"product_id\": {},\n \"pack_id\": {},\n \"image_sourcing\": \"<string>\",\n \"reference\": {},\n \"slide_count\": 123,\n \"slideshow_type\": \"<string>\",\n \"aspect_ratio\": \"<string>\",\n \"language\": \"<string>\",\n \"advanced_settings\": {\n \"font_size\": {},\n \"text_preset\": \"<string>\",\n \"text_width\": \"<string>\"\n },\n \"skip_ai\": true,\n \"slide_config\": {\n \"total_slides\": 123,\n \"slide_types\": [\n {}\n ],\n \"custom_images\": {},\n \"pinned_images\": {},\n \"slide_texts\": {},\n \"slide_text_elements\": {},\n \"pack_assignments\": {}\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/partner/v1/slideshows/generate")
.header("Content-Type", "application/json")
.body("{\n \"prompt\": \"<string>\",\n \"product_id\": {},\n \"pack_id\": {},\n \"image_sourcing\": \"<string>\",\n \"reference\": {},\n \"slide_count\": 123,\n \"slideshow_type\": \"<string>\",\n \"aspect_ratio\": \"<string>\",\n \"language\": \"<string>\",\n \"advanced_settings\": {\n \"font_size\": {},\n \"text_preset\": \"<string>\",\n \"text_width\": \"<string>\"\n },\n \"skip_ai\": true,\n \"slide_config\": {\n \"total_slides\": 123,\n \"slide_types\": [\n {}\n ],\n \"custom_images\": {},\n \"pinned_images\": {},\n \"slide_texts\": {},\n \"slide_text_elements\": {},\n \"pack_assignments\": {}\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/partner/v1/slideshows/generate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"prompt\": \"<string>\",\n \"product_id\": {},\n \"pack_id\": {},\n \"image_sourcing\": \"<string>\",\n \"reference\": {},\n \"slide_count\": 123,\n \"slideshow_type\": \"<string>\",\n \"aspect_ratio\": \"<string>\",\n \"language\": \"<string>\",\n \"advanced_settings\": {\n \"font_size\": {},\n \"text_preset\": \"<string>\",\n \"text_width\": \"<string>\"\n },\n \"skip_ai\": true,\n \"slide_config\": {\n \"total_slides\": 123,\n \"slide_types\": [\n {}\n ],\n \"custom_images\": {},\n \"pinned_images\": {},\n \"slide_texts\": {},\n \"slide_text_elements\": {},\n \"pack_assignments\": {}\n }\n}"
response = http.request(request)
puts response.read_bodySlideshows
Generate Slideshow
Generate AI-powered slideshows for TikTok photo carousels, Instagram carousels, Pinterest pins, and more. Supports AI generation from prompts, manual slide configuration, and mixed mode. The core content creation endpoint for OpenClaw agents.
POST
/
api
/
partner
/
v1
/
slideshows
/
generate
Generate Slideshow
curl --request POST \
--url https://api.example.com/api/partner/v1/slideshows/generate \
--header 'Content-Type: application/json' \
--data '
{
"prompt": "<string>",
"product_id": {},
"pack_id": {},
"image_sourcing": "<string>",
"reference": {},
"slide_count": 123,
"slideshow_type": "<string>",
"aspect_ratio": "<string>",
"language": "<string>",
"advanced_settings": {
"font_size": {},
"text_preset": "<string>",
"text_width": "<string>"
},
"skip_ai": true,
"slide_config": {
"total_slides": 123,
"slide_types": [
{}
],
"custom_images": {},
"pinned_images": {},
"slide_texts": {},
"slide_text_elements": {},
"pack_assignments": {}
}
}
'import requests
url = "https://api.example.com/api/partner/v1/slideshows/generate"
payload = {
"prompt": "<string>",
"product_id": {},
"pack_id": {},
"image_sourcing": "<string>",
"reference": {},
"slide_count": 123,
"slideshow_type": "<string>",
"aspect_ratio": "<string>",
"language": "<string>",
"advanced_settings": {
"font_size": {},
"text_preset": "<string>",
"text_width": "<string>"
},
"skip_ai": True,
"slide_config": {
"total_slides": 123,
"slide_types": [{}],
"custom_images": {},
"pinned_images": {},
"slide_texts": {},
"slide_text_elements": {},
"pack_assignments": {}
}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
prompt: '<string>',
product_id: {},
pack_id: {},
image_sourcing: '<string>',
reference: {},
slide_count: 123,
slideshow_type: '<string>',
aspect_ratio: '<string>',
language: '<string>',
advanced_settings: {font_size: {}, text_preset: '<string>', text_width: '<string>'},
skip_ai: true,
slide_config: {
total_slides: 123,
slide_types: [{}],
custom_images: {},
pinned_images: {},
slide_texts: {},
slide_text_elements: {},
pack_assignments: {}
}
})
};
fetch('https://api.example.com/api/partner/v1/slideshows/generate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/partner/v1/slideshows/generate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'prompt' => '<string>',
'product_id' => [
],
'pack_id' => [
],
'image_sourcing' => '<string>',
'reference' => [
],
'slide_count' => 123,
'slideshow_type' => '<string>',
'aspect_ratio' => '<string>',
'language' => '<string>',
'advanced_settings' => [
'font_size' => [
],
'text_preset' => '<string>',
'text_width' => '<string>'
],
'skip_ai' => true,
'slide_config' => [
'total_slides' => 123,
'slide_types' => [
[
]
],
'custom_images' => [
],
'pinned_images' => [
],
'slide_texts' => [
],
'slide_text_elements' => [
],
'pack_assignments' => [
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/partner/v1/slideshows/generate"
payload := strings.NewReader("{\n \"prompt\": \"<string>\",\n \"product_id\": {},\n \"pack_id\": {},\n \"image_sourcing\": \"<string>\",\n \"reference\": {},\n \"slide_count\": 123,\n \"slideshow_type\": \"<string>\",\n \"aspect_ratio\": \"<string>\",\n \"language\": \"<string>\",\n \"advanced_settings\": {\n \"font_size\": {},\n \"text_preset\": \"<string>\",\n \"text_width\": \"<string>\"\n },\n \"skip_ai\": true,\n \"slide_config\": {\n \"total_slides\": 123,\n \"slide_types\": [\n {}\n ],\n \"custom_images\": {},\n \"pinned_images\": {},\n \"slide_texts\": {},\n \"slide_text_elements\": {},\n \"pack_assignments\": {}\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/partner/v1/slideshows/generate")
.header("Content-Type", "application/json")
.body("{\n \"prompt\": \"<string>\",\n \"product_id\": {},\n \"pack_id\": {},\n \"image_sourcing\": \"<string>\",\n \"reference\": {},\n \"slide_count\": 123,\n \"slideshow_type\": \"<string>\",\n \"aspect_ratio\": \"<string>\",\n \"language\": \"<string>\",\n \"advanced_settings\": {\n \"font_size\": {},\n \"text_preset\": \"<string>\",\n \"text_width\": \"<string>\"\n },\n \"skip_ai\": true,\n \"slide_config\": {\n \"total_slides\": 123,\n \"slide_types\": [\n {}\n ],\n \"custom_images\": {},\n \"pinned_images\": {},\n \"slide_texts\": {},\n \"slide_text_elements\": {},\n \"pack_assignments\": {}\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/partner/v1/slideshows/generate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"prompt\": \"<string>\",\n \"product_id\": {},\n \"pack_id\": {},\n \"image_sourcing\": \"<string>\",\n \"reference\": {},\n \"slide_count\": 123,\n \"slideshow_type\": \"<string>\",\n \"aspect_ratio\": \"<string>\",\n \"language\": \"<string>\",\n \"advanced_settings\": {\n \"font_size\": {},\n \"text_preset\": \"<string>\",\n \"text_width\": \"<string>\"\n },\n \"skip_ai\": true,\n \"slide_config\": {\n \"total_slides\": 123,\n \"slide_types\": [\n {}\n ],\n \"custom_images\": {},\n \"pinned_images\": {},\n \"slide_texts\": {},\n \"slide_text_elements\": {},\n \"pack_assignments\": {}\n }\n}"
response = http.request(request)
puts response.read_bodyCreate a new slideshow in the authenticated key scope. This is the core content creation endpoint - your OpenClaw agent or automation script generates slideshows here, then publishes them to media-capable accounts via Create Post.
Pinterest auto mode (
Recreate a Viral Library post for a product (
Manual mode (
generate supports:
- AI generation from a prompt/product context
- manual initial slide setup (
skip_ai=true) - mixed setup with explicit
slide_config - recreating the format of a Viral Library post for your
product (
reference)
504 generation_timeout with the slideshow_id; poll
Get Slideshow until generation_status is complete or failed,
or retry with the same Idempotency-Key to wait on the same deck again.
Idempotency
Send a uniqueIdempotency-Key header for each slideshow you intend to create. Retrying the same
key with the same body replays the original result. When the header is omitted, Genviral derives a
stable key from the credential and request body for backward compatibility, so an identical
headerless request is treated as a retry rather than a new generation.
Body Parameters
string
Prompt used for AI text generation. Required unless
skip_ai=true or product_id is provided.string (UUID)
Optional product reference. Must exist in the authenticated key scope.
string (UUID)
Optional global image pack ID. Required whenever any generated slide uses
image_pack and no
per-slide pack_assignments are provided, unless image_sourcing allows Pinterest sourcing.string
Optional image sourcing mode. Default:
pack (backward compatible).pack— shuffle backgrounds frompack_idor per-slidepack_assignmentsonlypinterest_auto— run the whole-deck Pinterest agent after text generation;pack_idoptionalpinterest_then_pack— image search likepinterest_auto; a slide the search cannot fill takes an image from itspack_assignmentspack orpack_idinstead (no extra charge). Without a pack it behaves exactly likepinterest_auto, where an unfilled slide fails the request
object
Optional Viral Library post to recreate for your product or prompt, as
{ "viral_post_id": "<post id>" }. Genviral analyzes the post once (the first request for a
post can take 15–40 seconds longer; that time is part of the same wait) and builds one slide per
analyzed source slide: each keeps the source slide’s kind of shot, subject, copy role, copy
pattern, and length. The reference is style only: new words are written for your product and every
image is sourced by search. The source post’s text and images are never reused.Requires prompt or product_id. Not accepted together with slide_config, slide_count,
pack_id, skip_ai: true, or an image_sourcing other than pinterest_auto. Reading the post
counts against your Viral Library get rate limit.number
Optional target slide count (
1-10). Default: 5.string
Optional:
educational or personal. Default: educational.string
Optional:
9:16, 1:1, or 4:5. Default: 4:5.string
Optional language hint (2-32 chars).
object
Optional text styling defaults.
Show advanced_settings object
Show advanced_settings object
boolean
Optional. When
true, no AI copy is written: slides with text in slide_config.slide_texts or
slide_config.slide_text_elements keep it verbatim, and every other slide is image-only.
Images are still sourced from your packs, custom images, or Pinterest.object
Optional explicit per-slide setup.
Show slide_config object
Show slide_config object
number
required
Integer
1-10.array
required
Exact-length array matching
total_slides. Values: image_pack or
custom_image.object
Map of slide index -> custom image payload. Required for every
custom_image slide.object
Optional map of slide index -> pinned image URL.
object
Optional map of slide index -> text string.
object
Optional map of slide index -> text element array
(
content, x, y, optional id, font_size, width). Elements render verbatim at
their x/y (percent of the slide, 0–100), with font_size (8–200) and width
(1–100) when given and the deck’s text settings otherwise. Out-of-range values are
refused with 422 invalid_payload.object
Optional map of slide index -> pack UUID. Valid only for
image_pack
slides.All
slide_config map keys must be 0-based numeric indices in range. image_pack slides must
resolve a pack via pack_id, pack_assignments[index], or an image_sourcing mode that allows
Pinterest sourcing (pinterest_auto, pinterest_then_pack).Examples
AI mode (prompt + global pack)
cURL
curl --request POST \
--url https://www.genviral.io/api/partner/v1/slideshows/generate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: slideshow-discipline-quotes-v1' \
--data '{
"prompt": "5 discipline quotes",
"pack_id": "11111111-1111-1111-1111-111111111111",
"slide_count": 1,
"slideshow_type": "educational",
"aspect_ratio": "4:5",
"language": "en",
"advanced_settings": {
"font_size": 44,
"text_preset": "tiktok",
"text_width": "default"
}
}'
Pinterest auto mode (image_sourcing=pinterest_auto)
cURL
curl --request POST \
--url https://www.genviral.io/api/partner/v1/slideshows/generate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"prompt": "5 morning routine tips for busy parents",
"product_id": "22222222-2222-2222-2222-222222222222",
"image_sourcing": "pinterest_auto",
"slide_count": 5,
"slideshow_type": "educational",
"aspect_ratio": "4:5"
}'
Recreate a Viral Library post for a product (reference)
cURL
curl --request POST \
--url https://www.genviral.io/api/partner/v1/slideshows/generate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: meal-planner-like-viral-post-v1' \
--data '{
"product_id": "22222222-2222-2222-2222-222222222222",
"prompt": "Show how the app stops weekly grocery waste",
"reference": { "viral_post_id": "onthecarte-7510079309214731526" },
"aspect_ratio": "4:5"
}'
Manual mode (skip_ai=true)
cURL
curl --request POST \
--url https://www.genviral.io/api/partner/v1/slideshows/generate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"skip_ai": true,
"slide_config": {
"total_slides": 2,
"slide_types": ["custom_image", "image_pack"],
"custom_images": {
"0": {
"image_url": "https://cdn.example.com/custom-0.jpg",
"image_id": "hero_0"
}
},
"slide_text_elements": {
"0": [
{
"content": "Hook text",
"x": 50,
"y": 25
}
]
},
"slide_texts": {
"1": "Second slide text"
},
"pack_assignments": {
"1": "11111111-1111-1111-1111-111111111111"
}
}
}'
Response
Returns201 with the full slideshow object (same structure as
Get Slideshow).
Response
{
"ok": true,
"code": 201,
"message": "Slideshow generated",
"data": {
"id": "2ab58bb0-0c39-45c0-a4d5-b6852f9d7fc0",
"title": "5 discipline quotes",
"status": "draft",
"slideshow_type": "educational",
"product_id": null,
"original_prompt": "5 discipline quotes",
"preview_image_url": null,
"created_at": "2026-02-14T09:20:00.000Z",
"updated_at": "2026-02-14T09:20:00.000Z",
"last_rendered_at": null,
"slide_count": 1,
"settings": {
"image_pack_id": "11111111-1111-1111-1111-111111111111",
"aspect_ratio": "4:5",
"slideshow_type": "educational",
"advanced_settings": {
"font_size": 44,
"text_preset": "tiktok",
"text_width": "default"
},
"pack_assignments": null
},
"slides": [
{
"index": 0,
"image_url": "https://cdn.example.com/slides/discipline-1.jpg",
"rendered_image_url": null,
"text_elements": [
{
"id": "9fd0b2bd-a95a-4488-8b7a-bf1d18d2bcdf",
"content": "Discipline beats motivation",
"x": 50,
"y": 25,
"font_size": 48,
"width": 70,
"height": null,
"editable": true,
"style_preset": "tiktok",
"font_family": null,
"background_color": null,
"text_color": null,
"border_radius": null
}
],
"grid_images": null,
"grid_type": null,
"background_filters": null,
"image_overlays": null
}
]
}
}
Error Responses
400 invalid_json- body is not valid JSON422 invalid_payload- schema/validation failed422 pack_empty- a referenced pack is missing, outside key scope, or has no images404 viral_reference_not_found- thereferencepost does not exist or is not visible to this credential422 viral_reference_has_no_images- thereferencepost has no slide images to analyze429 rate_limited- areferencerequest hit the Viral Librarygetrate limit; seeretry_after_seconds503 viral_reference_analysis_unavailable- thereferencepost could not be analyzed right now (retryable: true); nothing was charged403 forbidden_product_access-product_iddoes not exist or is outside key scope409 conflict- theIdempotency-Keyis in use by a running request (retryable: true) or was used for a different request (retryable: false)401- authentication failed (missing/invalid/revoked token)402 subscription_required- active Creator/Professional/Business plan required402 insufficient_credits- buy a credit pack athttps://www.genviral.io/billing?tab=credits, then retry403 tier_not_allowed- Scheduler tier cannot use Partner API500 generate_failed- generation failed; includesslideshow_idwhen the deck was started504 generation_timeout- the deck is still generating; pollslideshow_idwith Get Slideshow
