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
Make your first book with a few API calls.
Setup
- Sign up at storymint.ca/signup
- Subscribe to the Developer plan, or to Max or Schools & Organizations (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
5 book formats
Coloring books, picture books, comics, chapter comics and novels Beta.
Custom branding
Set author name, series, art style, and choose from 8+ visual styles for your brand.
Longer books on higher plans
Page and chapter limits per book depend on your plan. See Rate limits.
PDF downloads
Download the finished book as a PDF on eligible plans; fetch page text and image URLs as JSON.
Audiobook generation
Generate an AI-narrated audiobook from a completed book (Max and Schools & Organizations).
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 PDF.
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. They work on the book endpoints (create, follow, read, download, edit and narrate books); account endpoints such as profile, API keys, teams, credits and billing need the signed-in token from Option 2.
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.
StoryMint API base URL
https://api.storymint.ca
This is the current StoryMint API endpoint. All paths below are relative to it. Example: POST https://api.storymint.ca/api/generate
Rate limits
Rate limits are applied per-account based on your plan tier:
| Plan | Books / month | Max picture pages | Max coloring pages | Max comic pages | Max novel chapters | API |
|---|---|---|---|---|---|---|
| Free | 1 preview book per month | preview | preview | — | — | — |
| Plus | 15 | 50 | 40 | 100 | 20 | — |
| Max | 50 | 50 | 40 | 100 | 30 | Included |
| Developer | 100 | 50 | 40 | — | — | 150 test calls |
| Schools & Orgs | 9,999 | 100 | 40 | 100 | 40 | Included |
When you exceed your quota, the API returns 402 with error_code: "quota" and a readable 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: coloring, 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. Coloring books use lineart_simple (big shapes for little hands), lineart_bold (default) or lineart_fine. |
story_model |
string | optional | Quality preset for the story text. The only preset today is standard, which is also the default, so you can leave it out. See quality presets. |
image_model |
string | optional | Quality preset for the illustrations. The only preset today is standard (the default). |
coloring_variant |
string | optional | Coloring books only: pure (pictures only, default) or story (a short caption under each picture). |
num_pages |
integer | optional | Number of pages (picture/comic) or chapters (novel). Clamped to your plan's limits — paid comics top out at 100 pages, picture books reach 100 pages on Schools & Organizations, and novels are beta with plan-based chapter caps. |
save_mode |
string | optional | Accepted for compatibility; the plan decides how long a book is kept. Every book is saved to the cloud. Plans that include the cloud library save books to cloud_library, kept permanently. The Free plan uses temporary_cloud: books are kept for 6 months. local_until_export is no longer supported and is treated like any other value. |
attachments |
array<string> | optional | Attachment ids returned by POST /api/attachments. Image references are vision-captioned and can drive SD Forge image-to-image rendering. |
Reference attachments
Upload files before calling /api/generate. StoryMint accepts up to 5 files, 10 MB each: PNG, JPEG, WebP, GIF, PDF, TXT, or Markdown.
curl -X POST https://storymint.ca/api/attachments \ -H "Authorization: Bearer $TOKEN" \ -F "files=@character-reference.png"
The response is { "ids": ["abc123ef"] }. Send those ids in the attachments field when you start generation.
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://api.storymint.ca"
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://api.storymint.ca";
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://api.storymint.ca/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://api.storymint.ca/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://api.storymint.ca/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://api.storymint.ca/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 every few seconds to track progress until status is done or failed. How long a book takes depends on its format and length.
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://api.storymint.ca"
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 as 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://api.storymint.ca";
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"
}
}
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 the PDF
Download the finished book as a PDF (application/pdf). Requires an eligible paid plan (Plus, Max, Developer or Schools & Organizations) or a book credit for that book.
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.pdf", "wb") as f:
f.write(resp.content)
print("Downloaded!")
else:
print("Error:", resp.json())
Story planner (Plan Mode)
Generate clarifying questions and suggested answers for a draft book prompt. Available on Max and Schools & Organizations plans.
{ "prompt": "A city gardener finds a tiny door under a tomato plant" }
Combine the original prompt with Plan Mode answers and return an enhanced prompt for /api/generate.
{
"prompt": "A city gardener finds a tiny door under a tomato plant",
"answers": [
{ "question": "Who opens the door?", "answer": "Maya, a curious gardener" }
]
}
Audiobook
Generate an AI-narrated audiobook from a completed book. Available on Max and Schools & Organizations plans. If narration is not available right now, the API returns an error with error_code instead of a partial file.
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 quality presets
Lists the quality presets available on your plan. Pass a preset id as story_model or image_model when generating, or leave both out to use standard.
Response
{
"presets": [
{ "id": "standard", "label": "Standard" }
]
}
StoryMint chooses and updates the underlying AI services itself, so presets stay stable when those change.
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={"Authorization": f"Bearer {ID_TOKEN}"},
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
Page and chapter limits are per book; book counts are per month. Here's what each plan unlocks:
Free
- A preview of one picture book or coloring book
- No comics or novels
- No API access
Plus
- 50 picture pages max
- 100 comic pages max
- 20 novel chapters max Beta
- 15 books / month
Max
- 50 picture pages max
- 100 comic pages max
- 30 novel chapters max Beta
- 50 books / month
- API access included
Schools & Organizations
- 100 picture pages max
- 100 comic pages max
- 40 novel chapters max Beta
- 9,999 books / month
- 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 100 on 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 |
|---|---|---|---|---|
| Coloring book | coloring |
Black-and-white line art, one scene per page, color cover; optional captions (coloring_variant: "story") |
Up to 40 pages (Free: preview) | Ages 2–12, by line style |
| Picture book | picture |
Full-page illustrations with overlaid text | Up to 100 pages (by plan) | Ages 2–7, simple stories |
| Comic | comic |
Panel-based comic pages with speech bubbles | Up to 100 pages | Ages 6–12, action stories |
| Chapter comic (manga) | chapter_comic |
Multi-chapter comic with cover page per chapter | Up to 100 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:
- Novels take longer to make than picture books, and longer novels take longer
- Longer novels are available on higher plans (see the limits above)
- Novel editing via chat is supported but works best for small tweaks
- PDF output includes professional formatting with chapter headings and page numbers
- Novels per month depend on your plan: Plus 3, Max 10, Schools & Organizations 9,999
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={"Authorization": f"Bearer {ID_TOKEN}"},
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://api.storymint.ca"
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 the PDF
dl = requests.get(f"{BASE}/api/download/{job_id}",
headers=HEADERS)
if dl.status_code == 200:
with open(f"{book['title']}.pdf", "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://api.storymint.ca";
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}.pdf`, 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}.pdf`;
// 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://api.storymint.ca";
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.pdf"), dlResp.body());
System.out.println("Downloaded!");
}
}
}
Full example — cURL
# 1. Generate a book
JOB=$(curl -s -X POST \
https://api.storymint.ca/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://api.storymint.ca/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://api.storymint.ca/api/book/$JOB_ID \
-H "X-API-Key: sm_your_key_here" | python3 -m json.tool
# 4. Download the PDF
curl -o book.pdf \
https://api.storymint.ca/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://api.storymint.ca";
// 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.pdf", 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://api.storymint.ca"
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.pdf")
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@storymint.ca.