StoryMint API Docs
Everything you need to generate AI-powered children's books programmatically. Custom branding, multiple formats, and code examples in every major language.
Quick start
Get your first book generated in under 5 minutes.
5-minute setup
- Sign up at story-mint.web.app/signup
- Subscribe to the Developer, Max, or Schools & Organizations plan (API access required)
- Create an API key at your API dashboard
- Make your first request — see the examples below
- Poll status until
status: "done", then download
4 Book formats
Picture books, comics, manga-style chapter comics, and full novels Beta with AI-generated covers.
Custom branding
Set author name, series, art style, and choose from 8+ visual styles for your brand.
Up to 200 pages
Higher plans unlock more pages per book. Comics max at 200, picture books at 100, novels at 40 chapters.
Full downloads
ZIP packages with PDF, individual pages, cover images, and metadata JSON.
Audiobook generation
Turn any completed book into an MP3 audiobook with AI narration (Max plan+).
AI chat editing
Send natural-language edit instructions and the AI rewrites pages in real time.
Lowest-code integration path
- Check auth first with
GET /api/pingso you know the key and plan are valid. - Generate with
POST /api/generate. - Poll
GET /api/status/{job_id}until you seedone. - Fetch or download with
GET /api/book/{job_id}for JSON orGET /api/download/{job_id}for the ZIP.
Authentication
Every request must include one of two authentication methods:
Option 1 — API key (recommended)
Pass your key in the X-API-Key header. API keys inherit your account's tier and quota.
X-API-Key: sm_your_key_here
Option 2 — Firebase Bearer token
If you're building a frontend, pass the Firebase ID token:
Authorization: Bearer <firebase_id_token>
Security: API keys are hashed with SHA-256 before storage — we never store your raw key. Treat your key like a password.
Base URL
https://felice-uninterrupted-vicente.ngrok-free.dev
All endpoint paths below are relative to this base. Example: POST https://felice-uninterrupted-vicente.ngrok-free.dev/api/generate
Rate limits
Rate limits are applied per-account based on your plan tier:
| Plan | Books / month | Max picture pages | Max comic pages | Max novel chapters | API calls |
|---|---|---|---|---|---|
| Free | 1 trial | 24 | — | — | — |
| Plus | 15 | 50 | 200 | 20 | — |
| Max | 50 | 50 | 200 | 30 | Unlimited |
| Developer | 100 | 50 | — | — | 150 test |
| Schools & Orgs | Unlimited | 100 | 200 | 40 | Unlimited |
When you exceed your quota, the API returns 403 with a descriptive error message. Quotas reset on the 1st of each month. Page limits are per-book, not per-month.
Generate a book
Start an asynchronous book generation job. Returns a job_id you'll use to poll status and retrieve the result.
Request body (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
prompt |
string | required | Story description, up to 600 characters. E.g. "A brave little raccoon who learns to share with forest friends" |
book_format |
string | optional | One of: picture, comic, chapter_comic, novel Beta. Defaults to picture. See formats. |
author |
string | optional | Author name printed on the cover and title page. Great for custom branding. |
series |
string | optional | Series name if this book belongs to a branded collection. Shown on the cover. |
art_style |
string | optional | Visual style for illustrations. See Art styles. Default: cartoon. |
ollama_model |
string | optional | Specific LLM model name from /api/models. Leave empty for auto-select. |
sd_model |
string | optional | Specific Stable Diffusion model from /api/models. Leave empty for auto-select. |
num_pages |
integer | optional | Number of pages (picture/comic) or chapters (novel). Clamped to your plan's limits — paid comics top out at 200 pages, picture books reach 100 pages on Schools & Organizations, and novels are beta with plan-based chapter caps. |
save_mode |
string | optional | temporary_cloud (default), cloud_library, or local_until_export. |
Response
{
"job_id": "a1b2c3d4",
"status": "running"
}
How it looks — modern prompt preview
This is what an API request looks like mapped to the StoryMint Create UI. Your API call sets all of these fields programmatically:
Every chip above maps to a JSON field in the request body. The prompt is the textarea, and options become key-value pairs.
Full example — create a branded picture book
import requests
API_KEY = "sm_your_key_here"
BASE = "https://felice-uninterrupted-vicente.ngrok-free.dev"
resp = requests.post(
f"{BASE}/api/generate",
headers={"X-API-Key": API_KEY},
json={
"prompt": "A brave little raccoon who learns to share",
"book_format": "picture",
"author": "My Brand Publishing",
"series": "Forest Friends",
"art_style": "watercolor",
"num_pages": 12,
},
)
data = resp.json()
print(f"Job started: {data['job_id']}")
const API_KEY = "sm_your_key_here";
const BASE = "https://felice-uninterrupted-vicente.ngrok-free.dev";
const resp = await fetch(`${BASE}/api/generate`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": API_KEY,
},
body: JSON.stringify({
prompt: "A brave little raccoon who learns to share",
book_format: "picture",
author: "My Brand Publishing",
series: "Forest Friends",
art_style: "watercolor",
num_pages: 12,
}),
});
const data = await resp.json();
console.log("Job started:", data.job_id);
import java.net.http.*;
import java.net.URI;
HttpClient client = HttpClient.newHttpClient();
String body = """
{
"prompt": "A brave little raccoon who learns to share",
"book_format": "picture",
"author": "My Brand Publishing",
"series": "Forest Friends",
"art_style": "watercolor",
"num_pages": 12
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://felice-uninterrupted-vicente.ngrok-free.dev/api/generate"))
.header("Content-Type", "application/json")
.header("X-API-Key", "sm_your_key_here")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
curl -X POST https://felice-uninterrupted-vicente.ngrok-free.dev/api/generate \
-H "Content-Type: application/json" \
-H "X-API-Key: sm_your_key_here" \
-d '{
"prompt": "A brave little raccoon who learns to share",
"book_format": "picture",
"author": "My Brand Publishing",
"series": "Forest Friends",
"art_style": "watercolor",
"num_pages": 12
}'
using System.Net.Http;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "sm_your_key_here");
var payload = new {
prompt = "A brave little raccoon who learns to share",
book_format = "picture",
author = "My Brand Publishing",
series = "Forest Friends",
art_style = "watercolor",
num_pages = 12,
};
var content = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8, "application/json");
var resp = await client.PostAsync(
"https://felice-uninterrupted-vicente.ngrok-free.dev/api/generate",
content);
var json = await resp.Content.ReadAsStringAsync();
Console.WriteLine(json);
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"io"
)
func main() {
body, _ := json.Marshal(map[string]interface{}{
"prompt": "A brave little raccoon who learns to share",
"book_format": "picture",
"author": "My Brand Publishing",
"series": "Forest Friends",
"art_style": "watercolor",
"num_pages": 12,
})
req, _ := http.NewRequest("POST",
"https://felice-uninterrupted-vicente.ngrok-free.dev/api/generate",
bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", "sm_your_key_here")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println(string(data))
}
Check status
Poll this endpoint to track generation progress. Typically takes 30–120 seconds for picture books, 2–5 minutes for comics, and 5–15 minutes for novels.
Response
{
"status": "running",
"step": "Generating illustrations",
"msg": "Drawing page 4 of 12...",
"pct": 45,
"title": "Rusty's Big Day",
"format": "picture",
"can_download": false
}
Status values: running → done or error.
Polling example (Python)
import time, requests
API_KEY = "sm_your_key_here"
BASE = "https://felice-uninterrupted-vicente.ngrok-free.dev"
job_id = "a1b2c3d4" # from /api/generate
while True:
r = requests.get(f"{BASE}/api/status/{job_id}",
headers={"X-API-Key": API_KEY})
info = r.json()
print(f"[{info.get('pct', 0)}%] {info.get('msg', '')}")
if info["status"] == "done":
print(f"Book ready! Title: {info.get('title')}")
break
elif info["status"] == "error":
print(f"Error: {info.get('msg')}")
break
time.sleep(3) # poll every 3 seconds
SSE progress stream
Real-time Server-Sent Events stream. More efficient than polling — you receive updates the instant they happen.
Event format
data: {"step":"generating","msg":"Writing page 3...","pct":25}
data: {"step":"illustrating","msg":"Drawing page 3...","pct":40}
data: {"step":"done","msg":"Book complete!","pct":100}
JavaScript example
const BASE = "https://felice-uninterrupted-vicente.ngrok-free.dev";
const jobId = "a1b2c3d4";
const evtSrc = new EventSource(`${BASE}/api/stream/${jobId}`);
evtSrc.onmessage = (e) => {
const data = JSON.parse(e.data);
console.log(`[${data.pct}%] ${data.msg}`);
if (data.step === "done" || data.step === "error") {
evtSrc.close();
}
};
Get book data
Retrieve the full book preview: pages, cover, text, metadata, and image URLs. Only available after the job status is done.
Response (picture book)
{
"title": "Rusty's Big Day",
"author": "My Brand Publishing",
"series": "Forest Friends",
"format": "picture",
"pages": [
{
"page_num": 1,
"text": "Once upon a time, in a hollow oak tree...",
"image_url": "/api/book/a1b2c3d4/image/page_01.png"
}
],
"cover_url": "/api/book/a1b2c3d4/image/cover.jpg",
"can_download": true,
"metadata": {
"total_pages": 12,
"art_style": "watercolor",
"model_used": "gemma3:12b"
}
}
Chat edit
Send natural-language instructions to modify a completed book. The AI rewrites the affected pages.
Request body
{
"message": "Make the ending happier and add a rainbow"
}
Response
{
"assistant_message": "I've updated pages 10-12 with a happier ending featuring a rainbow celebration!",
"pages": [ ... ]
}
Python example
resp = requests.post(
f"{BASE}/api/book/{job_id}/chat",
headers={"X-API-Key": API_KEY},
json={"message": "Change the main character's name to Luna"},
)
edit = resp.json()
print(edit["assistant_message"])
Download package
Download the complete book as a ZIP package. Requires Plus, Max, Developer, or the Schools & Organizations plan.
ZIP contents
book_package.zip ├── cover.jpg ├── book.pdf ├── pages/ │ ├── page_01.png │ ├── page_02.png │ └── ... └── metadata.json
Python download example
resp = requests.get(
f"{BASE}/api/download/{job_id}",
headers={"X-API-Key": API_KEY},
)
if resp.status_code == 200:
with open("my_book.zip", "wb") as f:
f.write(resp.content)
print("Downloaded!")
else:
print("Error:", resp.json())
Audiobook
Generate an MP3 audiobook with AI narration from a completed book. Available on Max and Schools & Organizations plans.
Response
Returns the MP3 file directly as audio/mpeg. Save it to disk:
resp = requests.post(
f"{BASE}/api/audiobook/{job_id}",
headers={"X-API-Key": API_KEY},
)
with open("audiobook.mp3", "wb") as f:
f.write(resp.content)
List models
Returns available AI models for your tier. Use model names in the ollama_model and sd_model fields when generating.
Response
{
"ollama": [
"gemma3:4b",
"gemma3:12b",
"deepseek-r1:8b",
"mistral:7b",
"qwen3:8b"
],
"sd": [
"animagineXLV31_v31",
"juggernautXL_v9",
"starlightXL_v3"
]
}
Models vary by tier. If you request a model not available on your plan, the API falls back to auto-select.
Ping auth check
Run a low-cost auth check before you generate. This confirms API access, tells you which plan is attached to the key, and increments the separate ping counter used by the Developer plan.
Response
{
"status": "ok",
"tier": "developer",
"plan_display_name": "Developer",
"api_test_calls_this_month": 12,
"api_test_calls_limit": 150,
"timestamp": "2026-03-27T18:41:22.115000+00:00"
}
Developer includes 150 ping calls per month. Max and Schools & Organizations include API access and currently have no separate ping cap.
Tier & usage
Get your current plan, usage counts, limits, and remaining quota.
Response
{
"tier": "developer",
"plan_display_name": "Developer",
"books_this_month": 12,
"books_limit": 100,
"next_reset": "2026-04-01",
"api_access": true,
"api_test_calls_this_month": 12,
"api_test_calls_limit": 150,
"full_download": true,
"max_pages": 50,
"novel_max_chapters": 0,
"org_type": ""
}
Custom branding
StoryMint is designed for white-label book generation. Use these fields to brand every book with your identity:
| Field | Where it appears | Example |
|---|---|---|
author |
Cover, title page, PDF metadata | "Maple Leaf Publishing" |
series |
Cover subtitle, spine (if applicable) | "Tiny Explorers Vol. 3" |
art_style |
All illustrations in the book | "watercolor" |
book_format |
Entire book layout and structure | "novel" |
Branding workflow
# Create a branded series of books
for topic in ["Space", "Ocean", "Jungle"]:
resp = requests.post(f"{BASE}/api/generate",
headers={"X-API-Key": API_KEY},
json={
"prompt": f"A child explores the {topic.lower()}",
"author": "Your Publishing Company",
"series": f"Little Explorers: {topic}",
"art_style": "watercolor",
"book_format": "picture",
})
print(f"Started {topic}: {resp.json()['job_id']}")
Profile customization
Read or update your publisher profile (display name, bio, website). This information appears on marketplace listings.
requests.put(f"{BASE}/api/profile",
headers={"X-API-Key": API_KEY},
json={
"display_name": "Maple Leaf Publishing",
"bio": "We create magical stories for curious kids.",
"website": "https://mapleleaf.example.com",
})
Art styles
Choose a visual style for all illustrations in your book. Pass the art_style value in your generate request.
cartoon
Bright, bold cartoon style. Best for younger audiences (ages 3–7). Default.
watercolor
Soft watercolor paintings. Elegant and whimsical, great for premium branding.
sketch
Pencil-sketch illustrations with a hand-drawn feel.
pixel
Retro pixel-art style. Fun for game-themed or nostalgic stories.
flat
Modern flat-design illustrations with clean lines and solid colours.
manga
Japanese manga style with expressive characters. Ideal for chapter comics.
western
Western comic-book style with dynamic inking and vivid panels.
graphic_novel
Mature graphic-novel aesthetic with detailed, cinematic panels.
Plans & page limits
The higher your plan, the more pages you can generate per book — up to 200 pages max. Here's what each tier unlocks:
Free
- 24 picture pages max
- 0 comic pages
- 0 novel chapters
- 1 trial book total
Plus
- 50 picture pages max
- 200 comic pages max
- 20 novel chapters max Beta
- 15 books / month
Max
- 50 picture pages max
- 200 comic pages max
- 30 novel chapters max Beta
- 50 books / month
- API access included
Schools & Organizations
- 100 picture pages max
- 200 comic pages max
- 40 novel chapters max Beta
- Unlimited books
- 10 teacher or team seats included
Developer
- 50 picture pages max
- 100 books / month
- 150 API test calls
- Full API access
- No comics or novels
Visual page scale
See how max pages scale by plan:
Comic pages go up to 200 across paid creative plans. Novels are in Beta and max out at 40 chapters on Schools & Organizations.
Book formats
| Format | Value | Output | Max pages | Best for |
|---|---|---|---|---|
| Picture book | picture |
Full-page illustrations with overlaid text | 24–100 pages (by plan) | Ages 2–7, simple stories |
| Comic | comic |
Panel-based comic pages with speech bubbles | Up to 200 pages | Ages 6–12, action stories |
| Chapter comic (manga) | chapter_comic |
Multi-chapter comic with cover page per chapter | Up to 200 pages | Ages 8+, serialized stories |
| Novel Beta | novel |
Long-form text with AI cover, formatted PDF | 5–40 chapters (by plan) | Ages 10+, 15k–60k+ words |
Beta Novel generation is currently in beta. Novels use AI to write full-length chapter books (15,000–60,000+ words) with auto-generated covers. During beta:
- Generation takes 5–15 minutes depending on chapter count
- Chapter quality improves with higher-tier LLMs (Max and Schools & Organizations plans)
- Novel editing via chat is supported but works best for small tweaks
- PDF output includes professional formatting with chapter headings and page numbers
- Novel availability is subject to plan — Plus gets 3/month, Max gets 10/month, Schools & Organizations is unlimited
API key management
List all your API keys with prefix, label, created/last-used dates, and status.
Create a new API key. The raw key (sm_...) is returned only once — save it immediately.
Request
{ "label": "My Production App" }
Response
{
"key": "sm_abc123xyz...",
"key_id": "k_8f2e1a",
"label": "My Production App"
}
Revoke an API key. It becomes permanently inactive. Create a new one if needed.
Teams (Schools & Organizations)
Schools & Organizations users can create shared workspaces with teacher or team-member seats, invite collaborators by email, and share finished books into a common library.
GET lists your teams. POST creates a new team.
Create team
resp = requests.post(f"{BASE}/api/teams",
headers={"X-API-Key": API_KEY},
json={"name": "Oakwood Elementary"})
team = resp.json()
print(f"Workspace: {team['id']} ({team['workspace_type']}), seats: {team['max_seats']}")
Invite a member by email. The response includes an invite id you can use for acceptance flows.
{
"email": "teacher@school.edu"
}
Accept a pending invite after the invited user signs in. Returns the updated workspace object.
List all books in the shared team library.
Share a finished book into the shared workspace library so every member can access it.
Full example — Python
Complete end-to-end workflow: generate, poll, download.
import requests, time
API_KEY = "sm_your_key_here"
BASE = "https://felice-uninterrupted-vicente.ngrok-free.dev"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
# 1. Generate
gen = requests.post(f"{BASE}/api/generate", headers=HEADERS, json={
"prompt": "A cat astronaut discovers a candy planet",
"book_format": "comic",
"author": "Starlight Studios",
"art_style": "cartoon",
"num_pages": 30,
}).json()
job_id = gen["job_id"]
print(f"Job: {job_id}")
# 2. Poll until done
while True:
status = requests.get(f"{BASE}/api/status/{job_id}",
headers=HEADERS).json()
print(f" [{status.get('pct', 0):3d}%] {status.get('msg', '')}")
if status["status"] in ("done", "error"):
break
time.sleep(3)
if status["status"] == "error":
print("Generation failed:", status.get("msg"))
exit(1)
# 3. Get book data
book = requests.get(f"{BASE}/api/book/{job_id}",
headers=HEADERS).json()
print(f"\nTitle: {book['title']}")
print(f"Pages: {len(book.get('pages', []))}")
# 4. AI edit
edit = requests.post(f"{BASE}/api/book/{job_id}/chat",
headers=HEADERS,
json={"message": "Add more humor to page 3"}).json()
print(f"Edit: {edit.get('assistant_message')}")
# 5. Download ZIP
dl = requests.get(f"{BASE}/api/download/{job_id}",
headers=HEADERS)
if dl.status_code == 200:
with open(f"{book['title']}.zip", "wb") as f:
f.write(dl.content)
print("Downloaded!")
else:
print("Download error:", dl.json())
Full example — JavaScript (Node.js / Browser)
const API_KEY = "sm_your_key_here";
const BASE = "https://felice-uninterrupted-vicente.ngrok-free.dev";
const headers = {
"Content-Type": "application/json",
"X-API-Key": API_KEY,
};
// 1. Generate
const genResp = await fetch(`${BASE}/api/generate`, {
method: "POST", headers,
body: JSON.stringify({
prompt: "A cat astronaut discovers a candy planet",
book_format: "comic",
author: "Starlight Studios",
art_style: "cartoon",
num_pages: 30,
}),
});
const { job_id } = await genResp.json();
console.log("Job:", job_id);
// 2. Poll
let status;
do {
await new Promise(r => setTimeout(r, 3000));
const r = await fetch(`${BASE}/api/status/${job_id}`, { headers });
status = await r.json();
console.log(` [${status.pct x 0}%] ${status.msg x ""}`);
} while (status.status === "running");
// 3. Get book
const book = await (await fetch(`${BASE}/api/book/${job_id}`,
{ headers })).json();
console.log("Title:", book.title);
// 4. Chat edit
const edit = await (await fetch(`${BASE}/api/book/${job_id}/chat`, {
method: "POST", headers,
body: JSON.stringify({ message: "Make the ending funnier" }),
})).json();
console.log("Edit:", edit.assistant_message);
// 5. Download (Node.js)
const dlResp = await fetch(`${BASE}/api/download/${job_id}`,
{ headers });
if (dlResp.ok) {
const fs = await import("fs");
const buf = Buffer.from(await dlResp.arrayBuffer());
fs.writeFileSync(`${book.title}.zip`, buf);
console.log("Downloaded!");
}
// 5b. Download (Browser)
// const blob = await dlResp.blob();
// const a = document.createElement("a");
// a.href = URL.createObjectURL(blob);
// a.download = `${book.title}.zip`;
// a.click();
Full example — Java
import java.net.http.*;
import java.net.URI;
import java.nio.file.*;
public class StoryMintExample {
static final String API_KEY = "sm_your_key_here";
static final String BASE = "https://felice-uninterrupted-vicente.ngrok-free.dev";
static final HttpClient client = HttpClient.newHttpClient();
static HttpRequest.Builder req(String path) {
return HttpRequest.newBuilder()
.uri(URI.create(BASE + path))
.header("X-API-Key", API_KEY)
.header("Content-Type", "application/json");
}
public static void main(String[] args) throws Exception {
// 1. Generate
String body = """
{
"prompt": "A cat astronaut discovers a candy planet",
"book_format": "comic",
"author": "Starlight Studios",
"art_style": "cartoon",
"num_pages": 30
}""";
var genResp = client.send(
req("/api/generate").POST(
HttpRequest.BodyPublishers.ofString(body)).build(),
HttpResponse.BodyHandlers.ofString());
System.out.println("Generate: " + genResp.body());
// Parse job_id from JSON response
String jobId = "PARSE_FROM_RESPONSE";
// 2. Poll status
while (true) {
var statusResp = client.send(
req("/api/status/" + jobId).GET().build(),
HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + statusResp.body());
if (statusResp.body().contains("\"done\"") ||
statusResp.body().contains("\"error\"")) break;
Thread.sleep(3000);
}
// 3. Download
var dlResp = client.send(
req("/api/download/" + jobId).GET().build(),
HttpResponse.BodyHandlers.ofByteArray());
if (dlResp.statusCode() == 200) {
Files.write(Path.of("book.zip"), dlResp.body());
System.out.println("Downloaded!");
}
}
}
Full example — cURL
# 1. Generate a book
JOB=$(curl -s -X POST \
https://felice-uninterrupted-vicente.ngrok-free.dev/api/generate \
-H "Content-Type: application/json" \
-H "X-API-Key: sm_your_key_here" \
-d '{
"prompt": "A cat astronaut discovers a candy planet",
"book_format": "comic",
"author": "Starlight Studios",
"art_style": "cartoon",
"num_pages": 30
}')
echo "Generate response: $JOB"
JOB_ID=$(echo $JOB | grep -o '"job_id":"[^"]*"' | cut -d'"' -f4)
# 2. Poll status until done
while true; do
STATUS=$(curl -s \
https://felice-uninterrupted-vicente.ngrok-free.dev/api/status/$JOB_ID \
-H "X-API-Key: sm_your_key_here")
echo "Status: $STATUS"
echo "$STATUS" | grep -q '"done"\|"error"' && break
sleep 3
done
# 3. Get book data
curl -s \
https://felice-uninterrupted-vicente.ngrok-free.dev/api/book/$JOB_ID \
-H "X-API-Key: sm_your_key_here" | python3 -m json.tool
# 4. Download ZIP
curl -o book.zip \
https://felice-uninterrupted-vicente.ngrok-free.dev/api/download/$JOB_ID \
-H "X-API-Key: sm_your_key_here"
Full example — C#
using System.Net.Http;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "sm_your_key_here");
var baseUrl = "https://felice-uninterrupted-vicente.ngrok-free.dev";
// 1. Generate
var genPayload = JsonSerializer.Serialize(new {
prompt = "A cat astronaut discovers a candy planet",
book_format = "comic",
author = "Starlight Studios",
art_style = "cartoon",
num_pages = 30,
});
var genResp = await client.PostAsync($"{baseUrl}/api/generate",
new StringContent(genPayload, Encoding.UTF8, "application/json"));
var genJson = JsonDocument.Parse(await genResp.Content.ReadAsStringAsync());
var jobId = genJson.RootElement.GetProperty("job_id").GetString();
Console.WriteLine($"Job: {jobId}");
// 2. Poll
while (true) {
var statusResp = await client.GetStringAsync($"{baseUrl}/api/status/{jobId}");
Console.WriteLine($"Status: {statusResp}");
if (statusResp.Contains("\"done\"") || statusResp.Contains("\"error\"")) break;
await Task.Delay(3000);
}
// 3. Download
var dlResp = await client.GetAsync($"{baseUrl}/api/download/{jobId}");
if (dlResp.IsSuccessStatusCode) {
var bytes = await dlResp.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("book.zip", bytes);
Console.WriteLine("Downloaded!");
}
Full example — Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const apiKey = "sm_your_key_here"
const base = "https://felice-uninterrupted-vicente.ngrok-free.dev"
func apiReq(method, path string, body interface{}) (*http.Response, error) {
var buf io.Reader
if body != nil {
b, _ := json.Marshal(body)
buf = bytes.NewBuffer(b)
}
req, _ := http.NewRequest(method, base+path, buf)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", apiKey)
return http.DefaultClient.Do(req)
}
func main() {
// 1. Generate
resp, _ := apiReq("POST", "/api/generate", map[string]interface{}{
"prompt": "A cat astronaut discovers a candy planet",
"book_format": "comic",
"author": "Starlight Studios",
"art_style": "cartoon",
"num_pages": 30,
})
var gen map[string]interface{}
json.NewDecoder(resp.Body).Decode(&gen)
resp.Body.Close()
jobID := gen["job_id"].(string)
fmt.Println("Job:", jobID)
// 2. Poll
for {
resp, _ := apiReq("GET", "/api/status/"+jobID, nil)
var st map[string]interface{}
json.NewDecoder(resp.Body).Decode(&st)
resp.Body.Close()
fmt.Printf(" [%.0f%%] %s\n", st["pct"], st["msg"])
if st["status"] == "done" || st["status"] == "error" {
break
}
time.Sleep(3 * time.Second)
}
// 3. Download
resp, _ = apiReq("GET", "/api/download/"+jobID, nil)
defer resp.Body.Close()
if resp.StatusCode == 200 {
f, _ := os.Create("book.zip")
io.Copy(f, resp.Body)
f.Close()
fmt.Println("Downloaded!")
}
}
Error codes
200Success201Resource created (API key, team)400Bad request — missing or invalid parameters401Unauthorized — missing or invalid API key / token403Forbidden — quota exceeded or feature not available on your plan404Not found — invalid job_id or resource429Too many requests — rate limited500Internal server error — retry after a few seconds503Service unavailable — generation engine temporarily offline
Error response format
{
"error": "Descriptive error message here",
"detail": "Additional context (optional)"
}
Handling errors (Python)
resp = requests.post(f"{BASE}/api/generate",
headers=HEADERS, json=payload)
if resp.status_code == 200:
job_id = resp.json()["job_id"]
elif resp.status_code == 403:
print("Quota exceeded:", resp.json().get("error"))
elif resp.status_code == 401:
print("Invalid API key. Check your X-API-Key header.")
else:
print(f"Error {resp.status_code}:", resp.json())
Webhooks
Generation-complete webhooks are not available in this public build yet. Use status polling or SSE streaming to track job progress instead.
Interested? Let us know at support@story-mint.com.