Developer documentation

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.

API access requires a paid plan. Only the Developer, Max, and Schools & Organizations plans include API keys and programmatic book generation. Compare plans →

Quick start

Make your first book with a few API calls.

Setup

  1. Sign up at storymint.ca/signup
  2. Subscribe to the Developer plan, or to Max or Schools & Organizations (API access required)
  3. Create an API key at your API dashboard
  4. Make your first request — see the examples below
  5. 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

  1. Check auth first with GET /api/ping so you know the key and plan are valid.
  2. Generate with POST /api/generate.
  3. Poll GET /api/status/{job_id} until you see done.
  4. Fetch or download with GET /api/book/{job_id} for JSON or GET /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:

PlanBooks / monthMax picture pagesMax coloring pagesMax comic pagesMax novel chaptersAPI
Free1 preview book per monthpreviewpreview———
Plus15504010020—
Max50504010030Included
Developer1005040——150 test calls
Schools & Orgs9,9991004010040Included

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

POST /api/generate Quota

Start an asynchronous book generation job. Returns a job_id you'll use to poll status and retrieve the result.

Request body (JSON)

ParameterTypeRequiredDescription
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:

Your API request → StoryMint Create screen
Generate
Format: picture
Pages: 12
Style: watercolor
Author: My Brand
Series: Forest Friends
Quality: standard

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

GET /api/status/{job_id}

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

GET /api/stream/{job_id}

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

GET /api/book/{job_id}

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

POST /api/book/{job_id}/chat

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

GET /api/download/{job_id} Paid plans

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)

POST /api/plan-questions Max+

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" }
POST /api/plan-enhance Max+

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

POST /api/audiobook/{job_id} Max+

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

GET /api/models

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

GET /api/ping

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 /api/tier

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:

FieldWhere it appearsExample
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

GET PUT /api/profile

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

CA$0
  • A preview of one picture book or coloring book
  • No comics or novels
  • No API access

Plus

CA$9.99/mo
  • 50 picture pages max
  • 100 comic pages max
  • 20 novel chapters max Beta
  • 15 books / month

Schools & Organizations

CA$99.99/mo
  • 100 picture pages max
  • 100 comic pages max
  • 40 novel chapters max Beta
  • 9,999 books / month
  • 10 teacher or team seats included

Developer

CA$24.99/mo
  • 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:

—Free
50Plus
50Max
50Dev
100School

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

FormatValueOutputMax pagesBest 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

GET /api/keys

List all your API keys with prefix, label, created/last-used dates, and status.

POST /api/keys

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"
}
DELETE /api/keys/{key_id}

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 POST /api/teams

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']}")
POST /api/teams/{team_id}/invite

Invite a member by email. The response includes an invite id you can use for acceptance flows.

{
  "email": "teacher@school.edu"
}
POST /api/teams/invite/{invite_id}/accept

Accept a pending invite after the invited user signs in. Returns the updated workspace object.

GET /api/teams/{team_id}/books

List all books in the shared team library.

POST /api/teams/{team_id}/books/{book_id}

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

  • 200 Success
  • 201 Resource created (API key, team)
  • 400 Bad request — missing or invalid parameters
  • 401 Unauthorized — missing or invalid API key / token
  • 403 Forbidden — quota exceeded or feature not available on your plan
  • 404 Not found — invalid job_id or resource
  • 429 Too many requests — rate limited
  • 500 Internal server error — retry after a few seconds
  • 503 Service 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.