# Inkroost — Full AI Agent Documentation > Inkroost is a creation and publishing platform for comics, webtoons, and manga. Independent creators build a series, define their characters and visual style, generate or upload artwork page by page, write the accompanying text layer, and publish chapters that readers discover and read on the web. ## Docs - [Machine-readable summary](https://inkroost.com/llms.txt): the short form of this file. - [Create a series skill](https://inkroost.com/skills/inkroost-create-series/SKILL.md): portable workflow for establishing a series, style, cast, locations, and props. - [Create a chapter skill](https://inkroost.com/skills/inkroost-create-chapter/SKILL.md): portable workflow for planning, generating, reviewing, finishing, and optionally submitting a chapter. - [FAQ and use cases](https://inkroost.com/faq): what the platform does, in question-and-answer form. - [Pricing](https://inkroost.com/pricing): credit packages, and what one AI generation costs. - [Support](https://inkroost.com/support): contact Inkroost about account, billing, publishing, moderation, or technical issues. - [Content policy](https://inkroost.com/content-policy): what may be published here, and how maturity ratings work. - [Terms of service](https://inkroost.com/terms): the agreement covering creator accounts and published work. - [Privacy policy](https://inkroost.com/privacy): what is collected, and why. - [Publish/Export API](https://inkroost.com/docs/publish-api): the integration contract for external platforms that receive published work — OAuth2 flow, package layout, asset specs. - [Ask Inky](https://inkroost.com/creator): the in-app assistant in the studio; signed-in creators ask it how-to questions about series, pages, publishing and credits, and it can plan and draw pages with them. Creators can also ask it to file a problem report for them; it drafts the report and the creator confirms before it is sent. ## Endpoints - [MCP endpoint](https://inkroost.com/mcp): one base URL serves every tool documented below. - [Sign in](https://inkroost.com/login): where a creator authenticates before issuing a token. - [Agent access tokens](https://inkroost.com/settings/api-tokens): issue and revoke bearer tokens, scoped by ability. - [Browse series](https://inkroost.com/browse): the published catalogue. - [Sitemap](https://inkroost.com/sitemap.xml): every public series, chapter and page URL. ## Platform Overview Inkroost covers the whole path from story idea to published pages. Creators register accounts, build a series (title, description, genres, maturity rating, reading direction), create character sheets with appearance and personality notes plus a style profile that fixes each character's look, generate AI cover, banner and page art using Inkroost credits, letter approved pages, write each page's text companion (alt text and transcript), and submit chapters for moderation review before publishing. Reading direction is per-series: left-to-right for Western comics and vertical-scroll webtoons, right-to-left for traditional manga. Nothing in the platform assumes a single format. ### How reader access works Every published chapter is either **free** or **supporter-only**. A supporter-only chapter unlocks for readers who support that creator on the creator's own connected platform — currently Patreon — which Inkroost verifies. **Inkroost credits pay for AI generation only; they never unlock a chapter.** ### What is publicly indexable Only chapters that are published AND approved, belonging to a published series, are publicly readable and listed in [the sitemap](https://inkroost.com/sitemap.xml). Drafts, chapters awaiting or failing review, scheduled-but-not-yet-live chapters, supporter-only chapters, and administratively held series are never listed. None of these states is one-way. A live chapter can be taken offline, and one waiting for review can be pulled back out of the queue (`unlock-chapter-for-edits-tool`, or Take offline in the Creator Studio); its pages then unlock for editing, nothing is deleted, and publishing it again sends the changed pages back through review. Publishing is never permanent, and changing an existing chapter never means creating a new one. After publishing, creators get a share step with the public series URL and one-tap share links, so the public page is the one they hand to readers. Canonical public URLs: ``` https://inkroost.com/series/{series-slug} https://inkroost.com/series/{series-slug}/chapters/{chapter-number}/{chapter-title-slug} https://inkroost.com/series/{series-slug}/chapters/{chapter-number}/pages/{page-number} ``` The page URL renders the artwork together with the creator's alt text and transcript — the readable, quotable form of that page. Writing that text layer (see `set-page-text-tool`) is what makes a visual work retrievable by search engines, screen readers, and AI assistants. A human-readable FAQ covering the same ground lives at https://inkroost.com/faq. ## MCP Server Inkroost exposes a single MCP (Model Context Protocol) server that covers every domain — story, characters, props, locations, art, publishing, credits. The server speaks the MCP 2025-03-26 specification over HTTPS (Streamable HTTP transport). ### Endpoint ``` POST https://inkroost.com/mcp ``` Every tool and resource lives behind that one URL. Tool discovery via `tools/list` returns the full catalog in a single page. Every tool in that catalog declares an `outputSchema` alongside its `inputSchema`: a JSON Schema for the tool's `structuredContent` on success — every key it can carry, each key's type and nullability, array item shapes, and which keys appear only in one mode of a tool that answers in more than one shape (for example `check-approval-status-tool` by chapter versus by series). The schemas are closed (`additionalProperties: false`), so a key that is not declared is never emitted. Read the `outputSchema` from `tools/list` as the authoritative description of a tool's result; the `Output` examples in this document are abbreviated illustrations. ### Connecting Three paths, all backed by the same authorization layer. Creators are prompted in-app to connect, and copy-ready setup for every path lives in the creator dashboard and under Settings → AI Agent Access. **Option 1 — ChatGPT plugin directory (ChatGPT)** Inkroost is listed in the ChatGPT plugin directory. There is no URL to paste and no developer mode to enable. 1. In ChatGPT, open **Plugins** in the left sidebar 2. Search for **Inkroost** 3. Click the **+** button to add it to your account 4. Sign in to Inkroost when prompted, then approve access Adding from the directory runs the same OAuth 2.1 authorization-code flow described below, so the resulting access token carries the umbrella `mcp:use` scope and every tool is available. **Option 2 — Custom Connector via OAuth (Claude.ai)** Claude.ai (Pro / Team / Enterprise) authorizes once and gets full access. No bearer-token plumbing. 1. In Claude, open the left sidebar → **Customize** → **Connectors** 2. Click the **+** button → **Add custom connector** 3. Paste `https://inkroost.com/mcp` and click **Add** 4. Sign in to Inkroost when prompted, then click **Allow** Under the hood, Claude.ai performs RFC 7591 Dynamic Client Registration, then the standard OAuth 2.1 authorization-code flow (PKCE required). The resulting access token carries the umbrella `mcp:use` scope, which authorizes every tool. OAuth discovery documents live at `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource`. **Option 3 — Bearer token (Claude Code, Cursor, Windsurf, OpenAI Agents, scripts)** For clients that don't speak the OAuth Custom Connector flow, generate a manual token with the granular abilities you need: 1. Log in at https://inkroost.com/login 2. Visit https://inkroost.com/settings/api-tokens 3. Create a token with the abilities your agent requires 4. Copy the token value — it is shown only once 5. Send it as a Bearer credential: ``` Authorization: Bearer ``` Example configs: ``` # Claude Code CLI claude mcp add --transport http inkroost https://inkroost.com/mcp \ --header "Authorization: Bearer YOUR_TOKEN_HERE" # Cursor / Windsurf (mcp.json) { "mcpServers": { "inkroost": { "url": "https://inkroost.com/mcp", "headers": { "Authorization": "Bearer YOUR_TOKEN_HERE" } } } } # OpenAI Agents SDK { "tools": [{ "type": "mcp", "server_label": "inkroost", "server_url": "https://inkroost.com/mcp", "headers": { "Authorization": "Bearer YOUR_TOKEN_HERE" }, "require_approval": "never" }] } ``` ### Token Abilities (manual tokens) Manual tokens are scoped. Pick the abilities your agent actually needs. | Ability | What it unlocks | |---------|-----------------| | `story:read` | Read series, story state, characters, props, locations, consistency context | | `story:write` | Create / update series, characters, props, locations, chapters, style profiles | | `art:generate` | Generate AI art (charges credits per generation) | | `publish:write` | Publish chapters and submit for review | | `credits:read` | Check balance, estimate generation costs, list packages | OAuth tokens (Option 1) hold the umbrella `mcp:use` scope, which counts as every ability. --- ## Reusable Workflow Prompts and Skills The MCP server exposes two reusable prompts through `prompts/list` and `prompts/get`: - `create-inkroost-series` — accepts an optional `brief` and guides the client through clarification, duplicate checking, series creation, the required series style profile, character profiles, location/prop sheets, and a final consistency check. - `create-inkroost-chapter` — accepts optional `series` and `request` arguments and guides the client through series resolution, material questions, plan and spend approval, structured page generation, visual review, ordered approval, the per-page text layer, order confirmation, and optional moderation submission. Both workflows use the client's native question or clarification mechanism when a material decision is missing. They never treat an approval refusal as a transient error, and they ask before unapproved credit spend or public submission. For clients that load Agent Skills instead of MCP prompts, the same maintained instructions are available directly: - https://inkroost.com/skills/inkroost-create-series/SKILL.md - https://inkroost.com/skills/inkroost-create-chapter/SKILL.md --- ## Tool Catalog 62 tools total. The server registers all of them under one endpoint; tools the calling token isn't authorized for are filtered out of `tools/list`. ### Discovery Read-only catalogue and search tools. Start here when you don't already know a series or chapter ID — they resolve names to the IDs every write tool needs. All are scoped to the authenticated creator's own data; you can never discover another creator's work. Any authenticated token may call them. #### Tool: `list-series-tool` List the caller's series with lightweight summary data — the entry point for discovering an existing library. **Input:** ```json { "status": "string (optional) — filter on series status (e.g. ongoing, completed, hiatus)", "per_page": "integer (optional) — 1–100, defaults to 50", "page": "integer (optional) — 1-based, defaults to 1" } ``` **Output:** ```json { "series": [ { "id": 42, "title": "Shadow Academy", "slug": "shadow-academy", "status": "ongoing", "description": "A truncated (≤200 char) synopsis…", "chapter_count": 3, "created_at": "2026-01-01T00:00:00+00:00", "updated_at": "2026-01-05T00:00:00+00:00" } ], "pagination": { "total": 1, "per_page": 50, "current_page": 1, "last_page": 1 } } ``` `chapter_count` is the live count of publicly-readable chapters, computed fresh on every call. **Example:** `list-series-tool { "status": "ongoing", "per_page": 20 }` #### Tool: `search-series-tool` Case-insensitive substring search across the caller's own series title and description. Use it to resolve a series a user names in conversation ("continue Shadow Academy") into a `series_id`. **Input:** ```json { "query": "string (required) — substring to match against title/description", "per_page": "integer (optional) — 1–100, defaults to 50", "page": "integer (optional) — 1-based, defaults to 1" } ``` **Output:** same series summary shape as `list-series-tool`, wrapped as `{ "query": "...", "series": [ … ], "pagination": { … } }`. **Example:** `search-series-tool { "query": "shadow" }` #### Tool: `list-skills-tool` List the reusable Inkroost skills: the maintained SKILL.md workflows for building a series or authoring a chapter. Same content as the `prompts/list` prompts, reachable as a tool for clients that load tools but not prompts. Free and read-only. **Input:** none. **Output:** ```json { "skills": [ { "slug": "inkroost-create-chapter", "title": "Create an Inkroost chapter", "description": "Plan, generate, review, finish, and optionally submit a chapter…", "url": "https://inkroost.com/skills/inkroost-create-chapter/SKILL.md" }, { "slug": "inkroost-create-series", "title": "Create an Inkroost series", "description": "Create and prepare a new comic, webtoon, or manga series…", "url": "https://inkroost.com/skills/inkroost-create-series/SKILL.md" } ] } ``` **Example:** `list-skills-tool {}` #### Tool: `get-skill-tool` Fetch one skill's full SKILL.md markdown by slug. The `content` is byte-for-byte the markdown served at the skill's public URL; read it and follow it as the operating procedure for the request. An unknown slug returns a "Skill not found" error that lists the valid slugs. Free and read-only. **Input:** ```json { "skill": "string (required) — a slug from list-skills-tool, e.g. inkroost-create-chapter" } ``` **Output:** ```json { "slug": "inkroost-create-chapter", "title": "Create an Inkroost chapter", "url": "https://inkroost.com/skills/inkroost-create-chapter/SKILL.md", "content": "---\nname: inkroost-create-chapter\n… full SKILL.md markdown …" } ``` **Example:** `get-skill-tool { "skill": "inkroost-create-chapter" }` #### Tool: `list-genres-tool` List every genre a series can be filed under, in the order the public browse page shows them. Call it before `create-series-tool` so the series is tagged with real genres instead of guessed ids: pass the ids as `genre_ids` or the slugs as `genre_slugs`. The `slug` is the same value the browse page filters on (`/browse?genre=mystery`). Free and read-only. **Input:** none. **Output:** ```json { "genres": [ { "id": 1, "slug": "action", "name": "Action" }, { "id": 2, "slug": "adventure", "name": "Adventure" }, { "id": 11, "slug": "mystery", "name": "Mystery" } ] } ``` **Example:** `list-genres-tool {}` #### Tool: `get-chapters-tool` List chapters you own, ordered by chapter number, with live page counts and text-version completeness. Omit `series_id` to list chapters across your whole library — that, combined with `needs_text_version`, is how you build a repair queue for older chapters whose pages were never given a text layer. **Input:** ```json { "series_id": "integer (optional) — a series you own. Omit to search every series you own", "needs_text_version": "boolean (optional) — return only chapters with at least one page missing alt_text or transcript, most incomplete first", "status": "string (optional) — filter on approval_status (draft, pending_review, approved, rejected)", "per_page": "integer (optional) — 1–100, defaults to 50", "page": "integer (optional) — 1-based, defaults to 1" } ``` **Output:** ```json { "series_id": 42, "chapters": [ { "id": 12, "number": 1, "title": "Chapter One", "description": "…", "series_id": 42, "access_mode": "free", "approval_status": "approved", "page_count": 8, "missing_alt_text_count": 0, "missing_transcript_count": 0, "text_version_complete": true, "published_at": "2026-01-03T00:00:00+00:00" } ], "chapters_needing_text_version": 0, "pagination": { "total": 1, "per_page": 50, "current_page": 1, "last_page": 1 } } ``` Text-version completeness is independent of `approval_status`: an approved, published chapter can still be missing its text layer entirely. Returns an error if a supplied `series_id` is not owned by the caller. **Example:** `get-chapters-tool { "series_id": 42 }` **Example (repair queue across every series you own):** `get-chapters-tool { "needs_text_version": true }` #### Tool: `get-pages-tool` List the pages within a chapter you own, ordered by page number — metadata plus each page's `image_url` (not the binary; use `get-page-image-tool` for bytes). **Input:** ```json { "chapter_id": "integer (required) — a chapter in a series you own" } ``` **Output:** ```json { "chapter_id": 12, "series_id": 42, "text_version": { "missing_alt_text_count": 2, "missing_transcript_count": 3, "text_version_complete": false }, "pages_missing_text_version": [104, 107, 109], "pages": [ { "id": 101, "page_number": 1, "image_url": "https://cdn.inkroost.com/…/page-1.png", "ai_generated": true, "ai_prompt": "A rooftop chase at dusk…", "has_annotations": false } ] } ``` Returns an error if the chapter belongs to another creator. **Example:** `get-pages-tool { "chapter_id": 12 }` ### Story #### Tool: `create-series-tool` Create a new series — comic, webtoon, or manga; the format follows from the reading direction and style profile you choose. Required ability: `story:write`. **Input:** ```json { "title": "string (required) — Series title", "description": "string (required) — Short synopsis, max 5000 chars", "genre_ids": "array (optional) genre ids to attach, as returned by list-genres-tool; an unknown id is refused", "genre_slugs": "array (optional) genre slugs to attach, as returned by list-genres-tool (e.g. mystery, slice-of-life); merged with genre_ids, an unknown slug is refused", "maturity_rating": "string (optional) — one of: all_ages, teen, mature", "reading_direction": "string (optional) — one of: ltr, rtl", "source_language": "string (optional, ISO 639-1) one of: en, ja, ko, zh, fr, es, de, pt, it; defaults to en when omitted, so pass it whenever the series is not in English. Carried in the manifest of every external publish and export", "content_origin": "string (optional) one of: human (no AI was used on any page), ai_assisted (AI draws all of the images), mixed (some pages are made by a human and some by AI; prompting or editing AI images still counts as AI-drawn); defaults to ai_assisted when omitted, because a series created through this tool was made with an agent. Pass human only when every page is artwork the creator drew themselves with no AI at all, mixed when some pages are theirs and some are generated. Readers see it as an origin badge, and the creator can correct it in Creator Studio", "default_style_profile": "object (optional) — Prefer set-series-style-profile-tool after creation for a guided structured shape" } ``` **Output:** ```json { "series_id": 42, "slug": "my-series-title", "title": "My Series Title", "status": "ongoing", "approval_status": "draft", "source_language": "en", "content_origin": "ai_assisted", "genres": [ { "id": 11, "slug": "mystery", "name": "Mystery" } ] } ``` `genres` echoes the genres that were applied, in browse order, so the tag can be confirmed straight from the create response. Genre changes after creation are made in the web app's series settings; no MCP tool edits them yet. #### Tool: `set-series-style-profile-tool` Set the series-wide default visual style profile. Call this immediately after `create-series-tool` and before any page generation — the profile is auto-injected into every generation prompt and is the single biggest lever for visual consistency across a manga. Required ability: `story:write`. **Input:** ```json { "series_id": "integer (required)", "style_preset": "string (optional) — a named look; the ids are listed under Art styles below. Inkroost fills photography and constraints from it, so those two groups may be omitted", "subject": { "name": "string", "age": "integer", "gender": "string", "hair": "string", "eyes": "string", "distinguishing_marks": "string", "typical_outfit": "string", "build": "string" }, "photography": { "style": "string", "shading": "string", "linework": "string", "panel_framing": "string" }, "background": { "setting": "string", "atmosphere": "string", "time_of_day": "string" }, "constraints": { "no_text": "boolean", "no_logos": "boolean", "aspect_ratio": "string", "color_palette": "string" } } ``` Without a preset, fill in every sub-field. Vague entries ("cool hair", "manga style") produce inconsistent art across pages — write detail the image model can actually render. The call replaces the existing profile (not a merge). Never assume manga: ask the creator which look they want before writing the profile, and treat a look they describe in their own words as just as valid as a preset. A preset is passed by id in `style_preset`; Inkroost writes the look itself, so the same name draws the same look in the studio, in Inky and over MCP, and you describe only the subject and the background. #### Art styles - **American comic** (bold brush inking, flat colour, heavy blacks and big lettering): `superhero` Superhero, `noir` Noir, `pulp` Golden Age, `graphic-novel` Graphic novel, `kirbyism` Kirbyism. - **Manga** (black and white ink, screentone, expressive faces and kinetic panels): `manga-bw` Manga B&W, `shonen` Shonen, `shojo` Shojo, `seinen` Seinen, `chibi` Chibi. - **Webtoon** (clean digital colour made for scrolling): `webtoon` Webtoon, `anime-colour` Anime colour. - **Cartoon** (simple shapes, thick outlines and big expressions): `cartoon-strip` Cartoon, `storybook` Storybook. - **European comic** (uniform clean lines or painted colour, detailed backgrounds): `ligne-claire` Ligne claire, `painted-album` Painted album. **Output:** ```json { "series_id": 42, "default_style_profile": { "subject": { "...": "..." }, "photography": { "...": "..." }, "background": { "...": "..." }, "constraints": { "...": "..." } }, "style_preset": "noir", "completeness": { "score": 17, "max": 19, "percentage": 89, "is_complete": true, "missing": ["subject.distinguishing_marks", "background.atmosphere"] } } ``` `style_preset` in the output names the preset the saved look matches, or is null for a look in the creator's own words. #### Tool: `get-story-state-tool` Retrieve the current state of a series including chapters, characters, prop sheets, location sheets, and the series style profile. Required ability: `story:read`. **Input:** ```json { "series_id": "integer (required)" } ``` **Output:** ```json { "id": 42, "title": "Shadow Realm", "slug": "shadow-realm", "description": "...", "status": "ongoing", "maturity_rating": "teen", "source_language": "en", "reading_direction": "ltr", "is_published": false, "approval_status": "draft", "default_style_profile": { "...": "..." }, "chapter_count": 3, "character_count": 2, "chapters": [ { "chapter_id": 1, "title": "Chapter One", "position": 1, "status": "draft" } ], "characters": [ { "character_id": 7, "name": "Kira", "appearance_notes": "Silver hair, blue almond eyes, warm golden skin.", "personality_notes": "Calm under pressure, dry wit.", "style_profile": { "...": "..." } } ] } ``` #### Tool: `get-consistency-context-tool` Retrieve the consistency context that will be auto-injected into generation prompts — series style profile, character sheets, prop sheets, location sheets, and completeness scores. Use this before planning or generating pages so you can self-correct any gaps first. Required ability: `story:read`. **Input:** ```json { "series_id": "integer (required)" } ``` **Output:** ```json { "series_id": 42, "title": "Shadow Realm", "default_style_profile": { "...": "..." }, "series_style_profile_completeness": { "score": 17, "max": 19, "percentage": 89, "is_complete": true, "missing": ["background.atmosphere"] }, "character_count": 2, "characters": [ { "character_id": 7, "name": "Kira", "appearance_notes": "Silver hair, blue almond eyes.", "personality_notes": "Calm under pressure.", "style_profile": { "...": "..." }, "completeness_score": { "score": 6, "max": 8, "percentage": 75, "is_complete": true, "missing": ["appearance:build", "appearance:outfit"] }, "style_profile_completeness": { "score": 12, "max": 19, "percentage": 63, "is_complete": false, "missing": ["subject.distinguishing_marks", "photography.linework", "background.atmosphere", "constraints.color_palette"] } } ] } ``` The `completeness_score` block reflects keyword coverage of `appearance_notes`. The `style_profile_completeness` block reflects how many leaf fields of the structured style profile are filled in (70% threshold for `is_complete`). Below-threshold characters may produce inconsistent images — fill them in via `set-character-style-profile-tool` before generating. #### Tool: `create-chapter-tool` Create a new chapter shell within an existing series. A chapter is the container for sequential pages — never a single page. Required ability: `story:write`. **Input:** ```json { "series_id": "integer (required)", "title": "string (required) — Chapter title", "content": "string (optional) — Chapter synopsis or description", "summary": "string (optional) — Short summary (alias for content)", "position": "number (optional) — Chapter number (e.g. 1, 1.5, 2). Auto-increments if omitted", "status": "string (optional) — one of: draft (default), published", "tags": "array (optional) — Tags for the chapter" } ``` **Output:** ```json { "chapter_id": 12, "series_id": 42, "title": "Chapter One: The Beginning", "position": 1, "status": "draft", "approval_status": "draft" } ``` Next steps: call `generate-pages-tool` (or `generate-pages-batch-tool`) multiple times to produce 8-24 pages, approve the best variants with `approve-pages-tool` / `batch-approve-pages-tool`, then publish with `publish-chapter-tool`. #### Tool: `update-chapter-tool` Update an existing chapter's title, description, status, or position. Required ability: `story:write`. **Input:** ```json { "chapter_id": "integer (required) — The chapter to update", "title": "string (optional)", "content": "string (optional) — Updated synopsis", "summary": "string (optional)", "status": "string (optional) — one of: draft, published", "position": "number (optional)" } ``` **Output:** Updated chapter object with `chapter_id`, `series_id`, `title`, `position`, `status`, `approval_status`, `updated: true`. --- ### Characters #### Tool: `create-character-tool` Create a new character sheet for a series. Required ability: `story:write`. **Input:** ```json { "series_id": "integer (required)", "name": "string (required)", "description": "string (optional)", "appearance_notes": "string (optional)", "personality_notes": "string (optional)", "style_profile": "object (optional) — Prefer set-character-style-profile-tool after creation for the guided structured shape" } ``` **Output:** ```json { "character_id": 7, "series_id": 42, "name": "Kira Tanaka" } ``` After creating, **immediately call `set-character-style-profile-tool`** to fix the character's visual identity. Without a structured style profile the character will drift visually across pages. #### Tool: `update-character-tool` Update an existing character sheet (name, description, narrative notes). For visual style updates use `set-character-style-profile-tool`. Required ability: `story:write`. **Input:** Same shape as `create-character-tool`, plus `character_id` (required). #### Tool: `set-character-style-profile-tool` Fix the character's visual identity so they render consistently across every page they appear in. When `character_ids` are passed to `generate-pages-tool`, this profile overrides the series default. Required ability: `story:write`. **Input:** ```json { "character_id": "integer (required)", "subject": { "name": "string (required) — Character's display name; anchors the profile", "age": "integer", "gender": "string", "hair": "string", "eyes": "string", "distinguishing_marks": "string", "typical_outfit": "string", "build": "string" }, "photography": { "...": "optional, see set-series-style-profile-tool" }, "background": { "...": "optional" }, "constraints": { "...": "optional" } } ``` The call replaces the existing `style_profile` (not a merge). **Output:** ```json { "character_id": 7, "series_id": 42, "name": "Kira Tanaka", "style_profile": { "...": "..." }, "completeness": { "score": 15, "max": 19, "percentage": 79, "is_complete": true, "missing": ["background.atmosphere"] } } ``` #### Tool: `get-character-sheet-tool` Retrieve a character sheet by ID. Required ability: `story:read`. **Input:** `{ "character_id": "integer (required)" }` **Output** includes the written notes, `completeness_score`, `style_profile_completeness`, `has_reference_sheet`, `share_status` and `share_url`. The same per-character fields appear in `get-consistency-context-tool`. #### The drawn model sheet Words fix the KIND of person. A **model sheet** fixes the person: front, three-quarter and side views, a set of expressions and a turnaround, drawn once from the notes on the sheet and then kept as that character's canonical reference. From then on the art model is given a picture to copy rather than prose to interpret, which is the single largest remaining source of character drift. `has_reference_sheet` reports whether one exists, on `get-character-sheet-tool` and on every character in `get-consistency-context-tool`. It is a **separate state from `completeness_score`**, in both directions: a character can be drawn and still have thin notes, and a character can score 100% and never have been drawn. Neither number implies the other, and a series whose written setup is complete may have no reference artwork at all. Drawing one is a Creator Studio action — it charges credits, so it happens behind an explicit priced confirmation that shows the cost, the balance and the balance after before anything is spent. There is no MCP path to draw one, and none to upload one: the bytes are private to the creator and served only through an ownership-checked route. What an external agent can usefully do is make the notes worth drawing from, then say that the drawing is the creator's next step. #### The public share card A creator can turn a drawn model sheet into a **share card** at its own public link — the artwork with the series and creator attribution and a link back to the work. Sharing is per character, never a default and never a side effect: the sheet is checked before the link works, and unsharing kills the old link immediately. `share_status` is one of `private`, `pending_review`, `shared` or `blocked`, and `share_url` is present only while the status is `shared`. Both are read-only over MCP. Never tell a creator a character has been shared: only they can share one, from Creator Studio. Places and objects have no share card of their own; their reference pictures stay private working material. #### Character Definition Best Practices The quality and consistency of AI-generated art depends heavily on how precisely each character is described. Vague descriptions produce inconsistent characters; detailed descriptions anchor the model. **Appearance notes and style profiles must be hyper-specific.** Include every visual attribute that matters: | Attribute | Bad (vague) | Good (specific) | |-----------|-------------|-----------------| | Hair | "dark hair" | "jet-black straight hair, cut bluntly at the jaw, with a single short fringe swept left across the forehead" | | Eyes | "blue eyes" | "pale ice-blue irises, almond-shaped, with a thin dark limbal ring and slightly upturned outer corners" | | Build | "athletic" | "lean mesomorphic build, 175 cm, wide shoulders tapering to a narrow waist, visible forearm musculature" | | Skin | "tan" | "warm golden-brown skin tone, no visible blemishes, light scar on the left chin 1 cm long" | | Clothing | "casual outfit" | "worn dark navy bomber jacket over a white ribbed tank top, fitted charcoal joggers, white low-top canvas sneakers" | | Distinctive features | — | "high cheekbones, a slightly prominent aquiline nose, and a thin vertical scar through the left eyebrow" | Personality notes should also capture mannerisms that translate to visual storytelling: posture (slouched vs. rigid), typical facial expressions (default scowl, warm grin), and habitual gestures. > **Why this matters:** Inkroost auto-injects each referenced character's style profile and appearance notes directly into the image prompt. The more precise your description, the more consistent characters appear across different panels and pages. --- ### Props & Devices A character sheet holds a face still across a chapter. Nothing held the **objects** still, and the drift is just as visible: a character's phone comes back with a different body, a different camera arrangement and a different colour from one panel to the next. A prop sheet is the character sheet for things. Write one for any object that appears in more than one panel and has a shape a reader could describe. **Phones and other handheld devices drift the most — do those first.** #### Tool: `create-prop-tool` Create a prop sheet for a recurring object. Required ability: `story:write`. **Input:** ```json { "series_id": "integer (required)", "name": "string (required) — unique within the series, e.g. \"Mika's phone\"", "category": "string (optional) — device | vehicle | weapon | garment | accessory | creature | other. Defaults to device", "aliases": "string[] (optional) — the other words your scene text uses for it", "description": "string (optional) — what it is and whose it is", "appearance_notes": "string (optional) — concrete visual facts. The field that does the work", "looks_like": "string (optional) — a curated shape key that anchors the object's geometry. See the shape library below", "real_world_model_anchor": "string (optional) — your own geometry statement, when no curated shape fits. Overrides looks_like. Shape only; brand and product names are rejected", "continuity_rules": "string (optional) — facts that must NEVER change, stated as rules", "style_profile": "object (optional) — per-prop style overrides" } ``` **Output:** ```json { "prop_id": 12, "series_id": 42, "name": "Mika's phone", "category": "device", "shape_anchor": "a thin bar-shaped smartphone with flat aluminium sides ...", "completeness_score": { "score": 4, "max": 4, "percentage": 100, "missing": [], "is_complete": true, "has_anchor": true, "anchor_source": "library" } } ``` `shape_anchor` is the RESOLVED sentence, not the key you sent — so you can check what the image model will actually be told without a second call. #### Tool: `update-prop-tool` Update an existing prop sheet. Only the fields you send are changed. Required ability: `story:write`. **Input:** Same shape as `create-prop-tool`, plus `prop_id` (required), minus `series_id`. #### Tool: `get-props-tool` List a series' prop sheets, each with its completeness score, its resolved `shape_anchor`, whether it has a canonical reference picture, and the exact `prompt_block` that will be injected into a page prompt. Required ability: `story:read`. **Input:** `{ "series_id": "integer (required)" }` Use `completeness_score.has_anchor` as your triage signal. `is_complete: false` means the sheet is doing nothing at all; `has_anchor: false` means it is doing half the job — the right kind of object every page, with its fine detail reinvented every page. #### How a prop reaches the image model A prop sheet is injected into a page's generation prompt in either of two ways: 1. **Explicitly** — its `prop_id` is passed to `generate-pages-tool`, `generate-pages-batch-tool` (batch-level or per page entry) or `regenerate-page-tool`. 2. **Automatically** — its name, or any of its `aliases`, appears in that page's scene text (the prompt, the continuity notes, or any panel's text). Matching is whole-word and case-insensitive. The second route is the one that matters in practice. A phone appears in a hundred beats across a chapter, and nobody remembers to attach an id to all hundred. Give the sheet the words your beats actually use — a sheet called "Mika's phone" should carry `"phone"` and `"her phone"` as aliases — and consistency stops depending on anyone remembering anything. #### Writing an appearance that works Concrete beats poetic. Name the shape, the material, the colour, the markings and the wear. - **Works:** "Matte charcoal slab phone, flat sides, one centred rear camera in a slim square bump, no case, a hairline crack across the top-left of the screen." - **Does nothing:** "A smartphone." Put the facts that keep slipping into `continuity_rules`, phrased as rules — "One rear camera, never two. The crack is always present and always top-left." Those are handed to the model as a hard constraint rather than a description. > **Why this matters:** a prop with a name and no `appearance_notes` occupies space in the prompt and tells the model nothing it had not already assumed. `completeness_score.is_complete` is false for exactly those sheets. #### Pinning the fine detail: the shape anchor A good `appearance_notes` gets you the right KIND of object every page. It does not get you the SAME object every page. The reported failure is precise: a character's phone stayed recognisably a phone while its back was invented afresh each time — one centred lens here, a differently shaped triple cluster in a different corner there, page after page. Prose cannot fix that, because prose leaves the geometry free. What fixes it is pointing at a shape the image model has already seen tens of thousands of times, so it reproduces one from memory instead of composing a new one. That is the shape anchor. Set exactly one of: - **`looks_like`** — a key from the curated shape library. Cheapest and most reliable for the objects that recur most. Get the full list from the `create-prop-tool` schema; it covers phones (by camera layout), tablets, laptops, headphones, earbuds, magnifying glasses, glasses, watches, swords, revolvers, cars, vans, motorbikes, bicycles and backpacks. - **`real_world_model_anchor`** — your own geometry statement, for anything the library does not cover. It overrides `looks_like` when both are set, because a creator who has written "the rim is octagonal, not round" has said something no curated entry can. Write an anchor the way you would describe a shape to someone drawing it blind: how many parts, arranged how, where, at what relative size. - **Works:** "A thin bar-shaped phone with flat metal sides, whose back carries one raised square block in the top-left corner holding exactly three round lenses in a triangle, with a flash dot beside them." - **Rejected:** "Looks like an iPhone 15 Pro." **Brand names are refused, by design.** Naming a maker or a product is how a logo ends up rendered on a published page, which is someone else's trademark in a creator's comic. `create-prop-tool` and `update-prop-tool` return an error naming the term and showing you what to write instead. Every anchor that IS accepted is injected into the prompt followed by an explicit instruction to draw no logo, brand name, model name or product text anywhere on the object. The anchor is a generation technique and nothing else: it never appears as visible copy anywhere in Inkroost, and no brand is ever recommended to a creator. The anchor is a strengthening signal, not a required field. Adding one raises `completeness_score` (`score` and `max` both go up by one); leaving it out changes nothing, so a sheet that read 100% before the anchor existed still reads 100%. `has_anchor` is the flag to act on. #### The canonical reference picture A creator can also attach one private picture of the actual object to a prop sheet, in Creator Studio. On **close-up, macro, insert and detail panels** — the ones where the object is the subject rather than a shape in someone's hand — that picture is passed to the image model as an input image, along with an explicit instruction to reproduce that exact object and to ignore the photograph's own lighting, framing and rendering. So the object is drawn from the real thing, in the art style of the page. Every other panel is drawn from the text anchor, deliberately: an image input is paid for on every variant of every page, and conditioning a wide street scene on a photograph of a phone drags the whole panel towards the photograph. **Establishing and wide shots are excluded for a second reason**: a page can carry only one input image, and on an establishing shot that slot belongs to the location sheet (below), not to an object. `get-props-tool` reports `has_reference_image` so you know which props have one. There is no MCP path to upload one — it is a Creator Studio action, because the bytes are private to the creator and are served only through an ownership-checked route. --- ### Locations & Sets A character sheet holds a face still. A prop sheet holds an object still. Nothing held **space** still, and the drift is at least as visible: `location` on a generation request was only a free-text hint, so the model designed a fresh building for every establishing shot — a different corner, a different awning, a different sign, a different window pattern. A location sheet is the character sheet for places. Write one for any place the story returns to. **Do the ones your chapters open on first** — establishing shots are where set drift is most obvious. And write it the way an **architect** writes a spec, not the way a novelist writes a setting: measurements, counts and named materials hold a building still; adjectives leave it free to be redesigned. #### Tool: `create-location-tool` Create a location sheet for a recurring place. Required ability: `story:write`. **Input:** ```json { "series_id": "integer (required)", "name": "string (required) — unique within the series, e.g. \"The Dead Hours shop\"", "aliases": "string[] (optional) — the other words your scene text uses for it", "description": "string (optional) — what this place is to the story", "appearance_notes": "string (optional) — concrete geometry. The field that does the work", "continuity_rules": "string (optional) — facts that must NEVER change, stated as rules", "style_profile": "object (optional) — per-location style overrides" } ``` **Output:** ```json { "location_id": 5, "series_id": 42, "name": "The Dead Hours shop", "completeness_score": { "score": 3, "max": 3, "percentage": 100, "missing": [], "is_complete": true } } ``` #### Tool: `update-location-tool` Update an existing location sheet. Only the fields you send are changed. Required ability: `story:write`. **Input:** Same shape as `create-location-tool`, plus `location_id` (required), minus `series_id`. #### Tool: `get-locations-tool` List a series' location sheets, each with its completeness score, the exact `prompt_block` that will be injected into a page prompt, and whether it carries a canonical reference image (`has_reference_image`) that establishing shots are conditioned on (`anchors_page_generation`), and its 3D spatial anchor state (`spatial_anchor`: `has_scene`, `fixture_count`, the `camera_angles` with their `has_render` / `render_stale` flags, and `has_birdseye` / `birdseye_stale` for the scene's top-down render). Required ability: `story:read`. **Input:** `{ "series_id": "integer (required)" }` #### How a location reaches the image model A location sheet is injected into a page's generation prompt in either of two ways: 1. **Explicitly** — its `location_id` is passed to `generate-pages-tool`, `generate-pages-batch-tool` (batch-level or per page entry) or `regenerate-page-tool`. 2. **Automatically** — its name, or any of its `aliases`, appears in that page's scene text (the prompt, the continuity notes, or any panel's text). Matching is whole-word and case-insensitive. The second route is the one that matters in practice, and more so than for props: the web chapter editor never writes the `location` field at all, so alias matching is the only thing that tells a page where it is set. Give the sheet the words your beats actually use — a sheet called "The Dead Hours shop" should carry `"the shop"` and `"the front"` as aliases. #### Precedence: the sheet, the `location` line, and the style profile Three things can describe where a page happens, and they are not equals: - A matched **location sheet** defines the place itself — the building, the room, the street. It always wins. - The page's own **`location`** string is kept, and means something narrower: where the camera is standing and what the light is doing ("outside, in the rain"). It is never suppressed, because the sheet does not carry that. - `style_profile.background.setting` is **dropped** whenever either of the above is present, so one idea is never stated twice in one prompt in two possibly-contradictory ways. #### Writing a location appearance that works Think like an architect describing a building to someone who has to draw it to scale. Geometry beats mood, and a number beats an adjective. Six things carry the weight: - **Size** — storey count and approximate dimensions or proportions: frontage width, ceiling height, the size of doors and windows. "Tall" is a feeling; "a 2.1m glazed door under a 3m awning" is a drawing. - **Layout** — stated as relative positions and counts, not as atmosphere. How many windows, on which side of the door, at what spacing. - **Materials and finishes** — named. Dark red stock brick, green canvas, painted timber, brushed brass. - **Signage** — the exact wording, roughly how big, and how it is mounted: hanging bracket, flush fascia, painted straight onto the brick. - **Context** — the street or the room it sits in, and what adjoins it. A corner building is a different building from the same facade mid-terrace. - **A real-world model** — where the place has a recognisable one, name the architectural typology. This is the same move the prop **shape anchor** makes: pointing at a form the image model has already seen tens of thousands of times gets you a shape reproduced from memory instead of composed from scratch. A Victorian corner pub, a 1970s American strip-mall unit, a Kyoto machiya, a Brutalist civic foyer. Put together, they hold a place still: - **Works:** "Two-storey Victorian corner shop of the London pub type, roughly 6m of frontage in dark red stock brick, a green canvas awning at about 3m over the pavement, two round windows each about 700mm across to the left of the door, a hanging painted sign on a wrought-iron bracket reading DEAD HOURS, one step up to a 2.1m glazed door with a brass handle." - **Does nothing:** "A cosy little shop." Put the facts that keep slipping into `continuity_rules`, phrased as rules — "The awning is always green. Always exactly two round windows, always left of the door. Always the corner building, never mid-terrace." #### Reference-image anchoring Words fix the KIND of building. They do not fix the building: "green awning, two round windows, corner brick" describes a family of shops, and the model picks a different member of that family each time. A creator can mark a canonical picture of a place in the Creator Studio — drawn from the sheet, or an approved page. When a location carries one and anchoring is on, **establishing and wide shots** of that place are generated conditioned on that image, so the actual facade is reproduced rather than re-invented. Close panels are deliberately left alone: conditioning every panel costs more and constrains composition for no visible gain. Anchoring is skipped in three cases, and every one of them is recorded on the job (`consistency_context_snapshot.location_anchor`, with a plain-language `reason`) rather than hidden — a creator whose set keeps changing must be able to find out why: 1. The platform's current art model does not accept an input image. The page falls back to the written sheet, and the Creator Studio says so. 2. The request already carried a reference image of its own — a style reference the creator chose. An explicit instruction beats an inference. 3. A prop's canonical picture took the slot. Rare by design: prop conditioning is scoped to close-ups, and location anchoring to establishing shots, so the two do not normally compete for the same page. > **Why this matters:** a location with a name and no `appearance_notes` occupies space in the prompt and tells the model nothing it had not already assumed from the word "shop". `completeness_score.is_complete` is false for exactly those sheets. #### The 3D spatial anchor and camera-angle conditioning Prose and even counted facts describe a family of rooms; a closed interior the story keeps returning to needs its geometry pinned. A location sheet can carry a **3D spatial anchor** — the room built once as deterministic primitive geometry — plus named **camera angles** into that room. No AI 3D generation is involved anywhere: the geometry is authored (in the Creator Studio's browser scene editor, or over MCP), so the same vertices exist on every read. **`spatial_scene`** (on `create-location-tool` / `update-location-tool`): `{ label?, room: { width, height, depth } in meters, meshes: [ { id?, kind, size: [w,h,d], position: [x,y,z], rotation_y?, color? } ] }`. Axis convention: x runs west→east, y is up, z runs north→south, and the entrance is the middle of the SOUTH wall; a mesh's `position` is its centre. From this the platform derives measured prompt facts (room dimensions, each fixture's size, wall adjacency and compass half of the room) that are injected into every page set in the location — and derives the checklist the post-generation vision audit uses, so a page that contradicts the geometry is flagged (and a count violation redrawn once automatically) before approval. Every derived direction is a fixed compass direction of the room itself, never a viewer-relative left/right: image models apply viewer-frame words in screen space, which mirrors the room in any shot whose camera does not stand at the entrance. Write your own `appearance_notes` and `continuity_rules` for interiors the same way — "the counter is ALWAYS along the EAST wall", not "on the right". **`camera_angles`**: `[ { id?, name, beats?: [words], position: [x,y,z], target: [x,y,z] } ]`. An angle binds to a page two ways: 1. **Explicitly** — pass its id as `camera_angle_id` on `generate-pages-tool`, `generate-pages-batch-tool` (batch-level or per entry) or `regenerate-page-tool`. An id that exists on no location in the series is refused, never silently ignored. 2. **Automatically** — the angle's `name` or one of its `beats` words appears in the page's scene text, by the same whole-word matching that binds a location's aliases. A bound angle always injects its deterministic viewpoint description ("camera in the south-centre of the room, at eye level, facing north, looking toward the north-centre of the room; the NORTH wall is ahead of the camera, in frame") into the prompt ahead of the scene text. Once a **render** of the angle has been captured in the Creator Studio scene editor, establishing and wide shots bound to the angle are additionally conditioned on that render as a reference image. A top-down **birdseye render** of the whole scene is captured automatically with every angle capture and rides ahead of the angle render in the two-image reference budget (south/entrance wall at the bottom of the frame), so the model reads what the angle's close geometry is instead of reinterpreting near planes as other furniture; where the location also has a usable blueprint, the labelled floor plan keeps the top-down slot instead. Close-ups are deliberately left alone, exactly as with reference-image anchoring. Editing the scene geometry marks every captured render stale (`render_stale`); stale renders are never sent, and pages fall back to the geometry text until the creator re-captures. A model with no input-image support falls back to the geometry text too, and the job records that it did. Camera-angle definitions are writable over MCP; the renders themselves are not — a render only enters the system through the Creator Studio's scene editor capture, because it must be produced from the canonical geometry. A capture that comes back nearly black (a camera saved inside or right behind geometry) is refused at save time rather than stored as conditioning. --- ### Art Art generation charges credits from the authenticated user's wallet. Check balance with `check-balance-tool` and remaining daily capacity with `get-daily-limit-status-tool` before queuing large batches. #### Tool: `generate-cover-tool` Generate an AI cover image for a series. Required ability: `art:generate`. **Input:** ```json { "series_id": "integer (required)", "prompt": "string (required) — Descriptive prompt, min 5, max 2000 chars", "title_text": "string (optional, max 500 chars) — the exact words to letter onto the cover, normally the series title. This is the ONLY way to get text into the artwork: without it the generator is told to draw no text of any kind, and asking for a title inside prompt alone is ignored", "style_profile": "object (optional) — Style overrides for this generation" } ``` **Output:** ```json { "series_id": 42, "status": "queued", "message": "Cover generation has been queued. Check back shortly for results.", "credits_charged": 1 } ``` #### Tool: `generate-banner-tool` Generate an AI banner image for a series. Uses a 16:9 landscape aspect ratio. Same input/output shape as `generate-cover-tool`. #### Tool: `annotate-series-art-tool` Letter a series' cover or banner: draw the title, a tagline, a volume number or a caption onto artwork that is **already on the series**, using the same typesetter `annotate-page-tool` uses for a page. Free — no credits are charged, because nothing is generated. Required ability: `art:generate`. Use this when the artwork exists and only the words are missing. When the artwork is still to be drawn, prefer `generate-cover-tool` / `generate-banner-tool` with `title_text` so the model designs the lettering into the illustration — that reads better than lettering added afterwards. For a title, `caption` at `top-center` reads as a title bar and `sfx` at `top-center` gives large outlined display lettering. The lettered copy is saved as a new file and put on the series; the artwork it replaces is not deleted. Lettering an approved series sends it back to moderation exactly as replacing its cover by hand does, and the response says so when it happens. **Input:** ```json { "series_id": "integer (required)", "kind": "cover | banner (required)", "annotations": [ { "type": "speech | thought | caption | narration | sfx (required)", "text": "string (required) — the words to draw (the container is measured and grown to fit)", "position": "top-left | top-center | top-right | center | bottom-left | bottom-center | bottom-right (optional)", "tail": "up | down | left | right | none (optional) — ignored for caption/narration/sfx", "font_scale": "float 0.5–2.0 (optional) — grow a title, shrink a credit line", "speaker": "string (optional) — prepended to speech/thought text" } ] } ``` **Output:** ```json { "series_id": 42, "kind": "cover", "new_image_path": "covers/lettered-....png", "annotations_applied": 1, "annotations_failed": 0, "annotations": [{"text": "BLOOD MOON", "type": "caption", "placement": "top-center", "status": "applied", "error": null}], "review_note": null, "message": "1 annotation(s) drawn onto the cover. It is on the series now." } ``` #### Tool: `generate-pages-tool` Generate AI comic/webtoon/manga page image variants for a chapter. Credits are charged per variant. Required ability: `art:generate`. The series `default_style_profile` and every referenced character `style_profile` are auto-injected into the prompt for visual consistency — set them first via `set-series-style-profile-tool` and `set-character-style-profile-tool`. A manga chapter is multiple sequential pages telling one sub-story. Call this once per beat (or use `generate-pages-batch-tool` for multiple beats in one call) — never settle for a single page per chapter. **Input:** ```json { "series_id": "integer (required)", "chapter_id": "integer (optional) — attach to an existing chapter; if omitted, a chapter is created after approval", "prompt": "string (required) — scene description, min 10, max 1000 chars", "style_prompt": "string (optional) — art style instructions; a series with no style profile and no keywords falls back to a neutral clean-comic default, never manga", "variant_count": "integer (optional) — number of variants 1-4. Defaults to 1", "style_profile": "object (optional) — override/patch the series default_style_profile for this page only", "no_character": "boolean (optional, default false) — when true the subject is stripped from the injected style profile so the AI renders a character-free scene (backgrounds, objects, establishing shots). Mirrors the 'No character' toggle in the web generator", "reference_image_url": "string (optional) — style-match mode: an image URL previously returned by an Inkroost tool (a page image from get-art-status-tool / get-page-image-tool, or a cover_url). The AI matches that image's art style instead of the series style profile. The web equivalent is 'Match my style' in Creator Studio, which also learns a reusable style profile and sheets from the creator's uploaded pages. Must be an Inkroost-hosted image — external URLs are rejected", "chapter_title": "string (optional) — title for the new chapter created at approval", "chapter_number": "number (optional) — chapter number for the new chapter", "character_ids": "array (optional) — IDs of characters appearing in this scene; their style profiles get injected automatically", "location_ids": "array (optional) — IDs of the location sheets this scene is set in; their appearance notes and continuity rules get injected automatically. Locations named in the scene text are injected without this too", "location": "string (optional) — e.g. 'Tokyo rooftop at dusk', 'underground cavern'", "time_of_day": "string (optional) — one of: dawn, morning, noon, afternoon, evening, dusk, night, midnight", "camera_angle": "string (optional) — e.g. 'low-angle', 'over-the-shoulder', 'birds-eye', 'dutch-angle'", "camera_angle_id": "string (optional) — bind this page to a saved camera angle on a location's 3D spatial anchor (ids from get-locations-tool spatial_anchor.camera_angles)", "framing": "string (optional) — e.g. 'extreme close-up', 'medium shot', 'wide establishing shot', 'two-shot'", "mood": "string (optional) — emotional tone, e.g. 'tense', 'hopeful', 'melancholic', 'action-packed'", "continuity_notes": "string (optional) — visual facts that must remain unchanged, e.g. 'Kira still wears the bandage on her left arm from Chapter 2'", "allow_text": "boolean (optional, default false) — by default no text, dialog, speech bubbles, or captions are rendered in images. Set to true only when you need the image model to handle in-image text (see guidance below)" } ``` **Output:** ```json { "job_id": 123, "series_id": 42, "chapter_id": null, "status": "queued", "variant_count": 2, "credits_charged": 2, "message": "Page generation has been queued. Use get-art-status-tool with this job_id to poll for results." } ``` #### `allow_text` — When and How to Use It **Only enable `allow_text: true` when you want the image model to handle speech bubbles and in-image text.** This trades layout control for contextual placement — the model can place dialog near the speaker, but results vary by model. **When you set `allow_text: true`, your `prompt` must include a precise script. Use the format below — speaker prefixes are stripped automatically so only the quoted text is rendered inside the bubbles:** ``` DIALOG SCRIPT (in reading order): Panel 1 — Kira: "I found it." Panel 2 — Ren: "Impossible. That vault has been sealed for a hundred years." Panel 3 — Kira: "Not anymore." Caption (top of page): Three days before the heist. ``` > **Format rule:** write each line as `Panel N — Speaker: "dialog text"`. **Do NOT write** `Speaker (position): "text"` — the parenthetical position info would render literally inside the bubble. **Requirements for `allow_text: true`:** - Name every speaker by their character name in `Panel N — Speaker: "text"` format - Write the exact dialog text — do not paraphrase - Specify reading order (panel 1 → 2 → 3); the model places bubbles contextually - Include caption or narration text with its position and type (caption, narration) **When NOT to use `allow_text`:** - Leave `allow_text: false` (default) for action scenes, establishing shots, or any page where dialog is not needed - For precise, reliable dialog control, generate clean art first and use `annotate-page-tool` afterwards > **Quality note:** in-image text quality varies by underlying model. If text is garbled, misspelled, or misplaced, discard the job and either retry with a refined prompt or switch to `annotate-page-tool` for post-generation annotation. #### Tool: `generate-pages-batch-tool` Queue multiple AI page generation jobs in one call. Each entry in the `pages` array creates one generation job. Credits are charged per variant per job. Returns an array of job IDs. Maximum 20 entries per batch. Required ability: `art:generate`. Each entry counts as one AI generation toward your **daily limit of 500/day**. Check remaining capacity with `get-daily-limit-status-tool` before queuing. **A batch is one ordered chain, and the pages see each other.** Page 1 is drawn first; its finished DRAWING is then attached to page 2 as an image input, page 2's drawing to page 3, and so on to the end of the run — an actual image, not a shared session id and not a description of what happened. That, alongside the character, prop and location sheets and any spatial anchor, is what keeps a face, an outfit, the light and the art style from drifting page to page. What follows from it: - **Order and timing.** Only page 1 starts when you call the tool. Each page starts when the page before it finishes, so a 10-page batch takes roughly ten times one page, not the time of one. Pages later in the run stay `pending` until their turn. - **Which drawing feeds the next page.** While no drawing has been kept for a page, the next page is drawn from that page's FIRST drawing, and `get-art-status-tool` reports that decision (`continuity.previous_page_variant_chosen_by: "automatic"`). Approve the variant you want with `approve-pages-tool` and every page drawn afterwards uses the one you kept (`"creator"`). - **A page that produces no drawing stops the run.** The remaining pages are not drawn from something older and are not drawn without the image: they are stopped and their credits returned. Draw that beat again (`regenerate-page-tool`, or queue it afresh) and the run carries on from it. - **Replacing a page marks the pages after it.** Regenerate page 4 and page 5 — already drawn from the old page 4 — is reported as `continuity.state: "stale"`. Nothing is redrawn and nothing extra is charged; redraw the later pages yourself if the change matters to them. - **A model that cannot read an input image cannot draw a chain.** The batch is refused before anything is charged when the platform's art model is known not to accept image input. A page that could not carry the image for any other reason says so on `get-art-status-tool` (`previous_page_image_attached: false` with a reason) and is never reported as though it had. **Input:** ```json { "series_id": "integer (required)", "chapter_id": "integer (optional) — attach all jobs to an existing chapter", "pages": [ { "prompt": "string (required) — scene description, min 10, max 1000 chars", "chapter_title": "string (optional)", "chapter_number": "number (optional)", "character_ids": "array (optional) — overrides batch-level character_ids for this page", "location_ids": "array (optional) — overrides batch-level location_ids for this page", "location": "string (optional)", "time_of_day": "string (optional) — dawn|morning|noon|afternoon|evening|dusk|night|midnight", "camera_angle": "string (optional)", "camera_angle_id": "string (optional) — bind this page to a saved camera angle on a location's 3D spatial anchor (ids from get-locations-tool spatial_anchor.camera_angles)", "framing": "string (optional)", "mood": "string (optional)", "continuity_notes": "string (optional)" } ], "style_prompt": "string (optional) — shared art style for all jobs", "variant_count": "integer (optional) — variants per job, 1-4. Defaults to 1", "style_profile": "object (optional) — shared style profile override; falls back to series default_style_profile when omitted", "chapter_title": "string (optional) — default chapter title for all jobs", "chapter_number": "number (optional) — starting chapter number (auto-increments per job)", "character_ids": "array (optional) — default character IDs for all pages", "location_ids": "array (optional) — default location IDs for all pages", "location": "string (optional)", "time_of_day": "string (optional) — dawn|morning|noon|afternoon|evening|dusk|night|midnight", "camera_angle": "string (optional)", "camera_angle_id": "string (optional) — bind this page to a saved camera angle on a location's 3D spatial anchor (ids from get-locations-tool spatial_anchor.camera_angles)", "framing": "string (optional)", "mood": "string (optional)", "continuity_notes": "string (optional)", "allow_text": "boolean (optional, default false)", "no_character": "boolean (optional, default false) — when true the subject is stripped from the injected style profile for every job so the AI renders character-free scenes. Mirrors the 'No character' toggle in the web generator" } ``` **Output:** ```json { "series_id": 42, "chapter_id": null, "jobs_queued": 5, "total_credits_charged": 10, "variant_count": 2, "jobs": [ { "job_id": 123, "prompt_preview": "A hero stands at the cliff...", "chapter_number": 1.0, "credits_charged": 2, "status": "queued" } ], "message": "5 page(s) queued as one ordered sequence: page 1 is generating now, and each page starts only when the page before it finishes — it is then drawn with that page's actual DRAWING attached as a reference image, alongside the character, prop and location sheets." } ``` `get-art-status-tool` returns a `continuity` block per job saying exactly what that page was drawn from: ```json { "continuity": { "state": "linked", "note": "This page continues from the drawing you kept for the page before it.", "batch_position": 3, "previous_page_job_id": 122, "previous_page_variant_id": 481, "previous_page_image_attached": true, "previous_page_image_fingerprint": "9f2c…", "previous_page_variant_chosen_by": "creator", "not_attached_reason": null } } ``` `state` is one of `linked`, `none` (no page before it), `waiting_for_choice`, `blocked` (the page before produced no drawing), `stale`, `unsupported_model`, `unavailable`, or `not_recorded` — which means the page was drawn before pages saw each other's art, and is NOT the same as having had nothing to continue from. #### Tool: `get-art-status-tool` Poll the status of an AI art generation job. Returns result URLs when the job is ready for review. Required ability: `art:generate`. **Input:** ```json { "job_id": "integer (optional) — the job ID to check", "series_id": "integer (optional) — return current cover and banner URLs for this series" } ``` **Output (while generating):** ```json { "job_id": 123, "status": "processing", "result_urls": [], "error_message": null, "completed_at": null, "inspect_hint": "To re-examine a single variant, call get-page-image-tool with this job_id (and optional variant_index)." } ``` **Output (once `status` is `review`):** a leading text block with the same JSON payload (now including `variant_indices_returned` and `result_urls`), followed by one **viewable image content block per generated variant** — so you can inspect the art (e.g. verify a page is text-free) without a second call. If a variant's file cannot be read yet (rare read-after-write lag), it is skipped from the inline images but still listed in `result_urls`; fetch it with `get-page-image-tool`. Status values: `pending`, `processing`, `review` (ready to approve — images are inlined), `completed`, `failed`. When status is `failed`, `error_message` contains the reason. #### Tool: `get-page-image-tool` Download the actual generated image **bytes** so you can see what was produced — inspect quality, compare variants, and decide which one to approve. `get-art-status-tool` returns URLs (which a connector often cannot fetch on its own); this tool returns the images themselves as viewable MCP image content. Required ability: `art:generate`. **Input:** ```json { "job_id": "integer (optional) — the generation job whose variants you want to view", "variant_index": "integer (optional) — zero-based index of a single variant (the same index used by approve-pages-tool). Omit to return every variant for the job", "page_id": "integer (optional) — alternative to job_id: download the image of an already-approved page" } ``` Provide either `job_id` or `page_id`. Each returned image is labelled with its zero-based variant index — the same index you pass to `approve-pages-tool`. Because full-resolution manga pages are large, prefer `variant_index` once you know which variant you want to re-examine (at most 4 images are returned per call). **Output:** a leading text block with JSON metadata (`job_id`, `variant_indices_returned`, and `variant_indices_omitted` when the payload cap is hit), followed by one image content block per variant. Ownership is enforced — only your own jobs and pages are returned. Typical loop: `generate-pages-tool` → `get-art-status-tool` (poll until `review`) → **`get-page-image-tool`** (view the variants) → `approve-pages-tool` with the `variant_indices` you chose. #### Tool: `get-daily-limit-status-tool` Check your current AI generation usage for today. **Call this before queuing large batches** to avoid hitting the daily cap mid-way through. Required ability: `art:generate`. **Daily limit: 500 AI generations per user per day, resets at midnight UTC.** No input required. **Output:** ```json { "used_today": 12, "daily_limit": 500, "remaining": 488, "resets_at": "2026-05-03T00:00:01+00:00", "message": "You have used 12 of your 500 daily AI generations. 488 remaining today." } ``` #### Tool: `approve-pages-tool` Approve AI-generated page variants and create Page records without the browser review UI. The job must have status `review`. Required ability: `art:generate`. Look at the variants before you approve one. `get-page-image-tool` returns the drawings as inspectable images; which of them reads as a page is not in the metadata. **One page per job where a redraw was automatic.** A job's variants can include a drawing Inkroost made by itself to correct a fixture miscount — `get-art-status-tool` marks it `origin: auto_fix_count` with `was_charged: false`. That drawing and the one it corrects are the same page, so a selection holding one of them creates exactly ONE page (the corrected drawing when its own re-audit came back clean, otherwise the original). Selecting several variants that are all genuinely separate drawings still creates one page each. **Input:** ```json { "job_id": "integer (required) — the generation job to approve", "variant_indices": "array (optional) — zero-based positions of variants to keep, e.g. [1] for the second, [0,1] for two separate pages. Omit it and ONE variant is kept: the one the continuity check recommended, else the first; the reply's message says which.", "discard": "boolean (optional) — set to true to discard all variants without creating pages. Credits are not refunded." } ``` **Output (pages approved):** ```json { "job_id": 123, "action": "approved", "chapter_id": 99, "series_id": 42, "pages_created": 2, "page_ids": [301, 302], "message": "Chapter \"Chapter 1\" created with 2 page(s)." } ``` #### Tool: `batch-approve-pages-tool` Approve multiple AI generation jobs at once. Accepts up to 50 job IDs per call and processes each atomically. Supports partial success — if one job fails, the others continue. The one-page rule for an automatic corrective redraw described under `approve-pages-tool` applies to every job in the batch. Required ability: `art:generate`. **Input:** ```json { "job_ids": [123, 124, 125], "variant_indices": "array (optional) — zero-based variant indices applied uniformly to every job. Omit it and each job keeps ONE variant: the one the continuity check recommended, else the first", "discard": "boolean (optional) — discard all variants in every job without creating pages" } ``` **Output:** ```json { "total": 3, "approved": 2, "failed": 1, "summary": "2 of 3 job(s) approved. 1 failed — see results for details.", "results": [ { "job_id": 123, "action": "approved", "chapter_id": 99, "pages_created": 2 }, { "job_id": 124, "action": "approved", "chapter_id": 100, "pages_created": 2 }, { "job_id": 125, "error": "Job 125 not found or you do not own it." } ] } ``` #### Tool: `annotate-page-tool` Letter an approved page like a human typesetter: real **speech bubbles** (white oval + black outline + a triangular **tail** pointing toward the speaker), **thought bubbles** (soft bluish cloud ringed with scalloped bumps + a trail of shrinking circles toward the character), and **fitted caption/narration boxes**. Text is measured before drawing and the container is grown to fit it, so lettering never clips or overflows. A reliable way to add readable dialog to manga pages when in-image text is needed. Required ability: `art:generate`. > **TEXT OVERLAP WARNING:** Agents have no visibility into image composition. Fixed-position annotations frequently overlap with generated art because manga pages are centre-focused and the agent cannot see where characters, panels, or backgrounds are placed. **Preferred approach:** use `generate-pages-tool` with `style_prompt` describing where dialog should appear and `allow_text=true` so the image generator can place text context-aware. **If `annotate-page-tool` must be used:** omit `position` to let the server choose a type-based default (speech/thought → `bottom-center`, caption → `top-center`, narration → `bottom-left`), or explicitly use `bottom-*` positions for dialog and `top-*` positions for captions to minimise overlap with centre-focused art. **Input:** ```json { "page_id": "integer (required)", "annotations": [ { "type": "speech | thought | caption | narration | sfx (required)", "text": "string (required) — the text content to render (the bubble/box is measured and grown to fit it)", "position": "top-left | top-center | top-right | center | bottom-left | bottom-center | bottom-right (optional)", "tail": "up | down | left | right | none (optional) — direction a speech/thought bubble tail points toward the speaker; derived from position when omitted (top-* → up, bottom-*/center → down). Ignored for caption/narration/sfx", "font_scale": "float 0.5–2.0 (optional) — shrink for a whisper, grow for a shout; clamped to range", "speaker": "string (optional) — character name prepended to speech/thought text" } ] } ``` **Output:** ```json { "page_id": 301, "chapter_id": 99, "new_image_path": "https://inkroost.com/media/pages/annotated-abc123.png", "annotations_applied": 2, "message": "2 annotation(s) applied successfully. The page image has been updated." } ``` **Annotation types:** - `speech` — white oval speech bubble with a black outline and a triangular tail toward the speaker, dark text (dialog spoken aloud) - `thought` — soft bluish cloud bubble (scalloped edge) with a trail of shrinking circles toward the character, dark text (internal monologue) - `caption` — dark semi-transparent bar fitted to the text, white text (scene description) - `narration` — darker navy box fitted to the text, white text (narrator voice-over) - `sfx` — bold, black-outlined onomatopoeia with no container (sound effects) **Human-like lettering tips:** use speech bubbles with a tail toward the speaker for spoken dialog, thought bubbles for internal monologue, captions/top-* for narration, and `sfx` for action sounds. All lettering is measured and fitted so it never overflows its bubble or box. #### Tool: `reorder-pages-tool` Reorder the pages of a chapter so they read in a new sequence. You supply the chapter's page IDs in the exact order you want them to appear. **Editable only while the chapter is a draft** — pages can be reordered when the chapter is a draft (never submitted) or was rejected and sent back for revision. Once a chapter is submitted for review or approved, its page layout is locked. Required ability: `art:generate`. `page_order` must list every current page of the chapter exactly once — missing, extra, duplicate, or foreign page IDs are rejected with a clear error. **Input:** ```json { "series_id": "integer (required)", "chapter_id": "integer (optional) — required unless chapter_number is given", "chapter_number": "number (optional) — locate the chapter by its number instead of its ID", "page_order": "array (required) — the chapter's page IDs in the desired final reading order (first page first)" } ``` **Output:** ```json { "chapter_id": 99, "series_id": 42, "page_count": 3, "pages": [ { "page_id": 303, "page_number": 1 }, { "page_id": 301, "page_number": 2 }, { "page_id": 302, "page_number": 3 } ], "message": "3 page(s) reordered." } ``` #### Tool: `insert-page-tool` Insert an existing Inkroost-hosted image into a chapter as a new page at a chosen position. `image_url` must be a URL previously returned by another Inkroost tool (e.g. a page image from `get-art-status-tool` or `get-page-image-tool`, or an approved page) — external URLs and missing files are rejected. **Editable only while the chapter is a draft** — pages can be inserted when the chapter is a draft (never submitted) or was rejected and sent back for revision. Once a chapter is submitted for review or approved, its page layout is locked. Required ability: `art:generate`. Pass an optional 1-based `position`: the new page lands there and every page at or after it shifts down by one. Omit `position` (or pass null) to append the page to the end. Out-of-range positions are clamped to `[1, page_count + 1]`. **Input:** ```json { "series_id": "integer (required)", "chapter_id": "integer (optional) — required unless chapter_number is given", "chapter_number": "number (optional) — locate the chapter by its number instead of its ID", "image_url": "string (required) — an Inkroost-hosted image URL returned by another tool", "position": "integer (optional) — 1-based insertion point; omit to append" } ``` **Output:** ```json { "page_id": 304, "chapter_id": 99, "series_id": 42, "position": 2, "page_count": 4, "message": "Page inserted at position 2. The chapter now has 4 page(s)." } ``` #### Tool: `delete-page-tool` Delete a single page from a chapter. The surviving pages are automatically re-sequenced to a gapless 1..N run and the chapter's `page_count` is refreshed; the underlying image file is left in storage. **Editable only while the chapter is a draft** — pages can be deleted when the chapter is a draft (never submitted) or was rejected and sent back for revision. Once a chapter is submitted for review or approved, its pages are locked. Required ability: `art:generate`. **Input:** ```json { "page_id": "integer (required) — must belong to a chapter in a series you own", "series_id": "integer (optional) — for symmetry with the other page tools; ownership is always checked by page_id" } ``` **Output:** ```json { "deleted_page_id": 302, "chapter_id": 99, "series_id": 42, "page_count": 3, "message": "Page deleted. The remaining 3 page(s) were re-sequenced to a gapless 1..3 run." } ``` #### Tool: `regenerate-page-tool` Regenerate the artwork of ONE existing page in place, keeping its position in the chapter. Credits are charged per variant. Returns a `job_id` for polling with `get-art-status-tool` — poll until status is `review`, then approve **exactly one** variant with `approve-pages-tool` and it replaces the page's image while keeping the same `page_number`. **Editable only while the chapter is a draft** — a page can be regenerated when its chapter is a draft (never submitted) or was rejected and sent back for revision. Once a chapter is submitted for review or approved, its pages are locked. Required ability: `art:generate`. By default the page's original prompt is reused; pass a new `prompt` to steer the regeneration. Pass `character_ids` so the right character style profiles are injected. This is the async, single-page counterpart to the deterministic `insert-page-tool` — use it when you want the AI to re-draw a page you already have. **Input:** ```json { "series_id": "integer (required)", "page_id": "integer (required) — the existing page to regenerate; must belong to a draft/rejected chapter you own", "prompt": "string (optional, min 10, max 1000) — defaults to the page's original prompt", "style_prompt": "string (optional) — defaults to manga style", "variant_count": "integer (optional, 1–4) — defaults to 1; approve exactly one", "style_profile": "object (optional)", "no_character": "boolean (optional) — strip the subject for a character-free scene", "reference_image_url": "string (optional) — an Inkroost-hosted image URL for style-match mode", "character_ids": "array (optional)", "location_ids": "array (optional)", "location": "string (optional)", "time_of_day": "string (optional) — dawn|morning|noon|afternoon|evening|dusk|night|midnight", "camera_angle": "string (optional)", "camera_angle_id": "string (optional) — bind this page to a saved camera angle on a location's 3D spatial anchor (ids from get-locations-tool spatial_anchor.camera_angles)", "framing": "string (optional)", "mood": "string (optional)", "continuity_notes": "string (optional)", "allow_text": "boolean (optional) — defaults to false" } ``` **Output:** ```json { "job_id": 512, "series_id": 42, "chapter_id": 99, "page_id": 302, "page_number": 2, "status": "queued", "variant_count": 1, "credits_charged": 1, "message": "Page regeneration queued. Poll get-art-status-tool with this job_id until status is \"review\", then approve exactly one variant with approve-pages-tool — it replaces page 2 in place, keeping its position." } ``` --- #### Tool: `set-page-text-tool` Set the text companion for an approved page: a short accessibility `alt_text` (rendered as the page image's `alt` attribute) and a longer `transcript` (scene description, visible characters, dialogue in reading order) shown in the public "Text Version" section of the reader and on the page's own public URL. This is a genuine reader-facing accessibility and discovery layer — write real descriptive prose, never keyword lists. Call it for every approved page: without this text layer a visual chapter is invisible to search engines, screen readers, and AI assistants. Required ability: `story:write`. Accepts either one page or a batch. Repairing a 30-page chapter is one call, not thirty. **Input (single page):** ```json { "page_id": "integer (required) — Page must belong to a series you own", "alt_text": "string (optional) — Short description of the page image. Empty string clears it", "transcript": "string (optional) — Full transcript: scene, visible characters, dialogue in reading order (max 5000 chars). Empty string clears it" } ``` **Input (many pages):** ```json { "pages": [ { "page_id": 104, "alt_text": "…", "transcript": "…" }, { "page_id": 107, "alt_text": "…", "transcript": "…" } ] } ``` Up to 100 entries. Ownership is re-checked per page, and results are per entry: one bad id is reported on its own and the rest are still written. Feed `pages_missing_text_version` from `get-pages-tool` straight into this array. Omitting a field leaves it unchanged; passing an empty string clears it. **Example:** `set-page-text-tool { "page_id": 512, "alt_text": "Kaito stands at the dojo gate at dusk, gripping his sword.", "transcript": "Exterior, dusk. Kaito faces the closed dojo gate...\nKAITO: \"Then I'll wait.\"" }` --- #### Tool: `confirm-page-order-tool` Record that a chapter's pages were reviewed and read in the intended narrative order. "Has all its images" and "reads in a coherent order" are separate quality signals; this one is advisory and never hard-blocks submission or publishing. The confirmation is cleared automatically by any page create, delete, or reorder, so it can only ever be true for the exact page set it was confirmed against. Re-confirm after changing a chapter's pages. Required ability: `story:write`. **Input:** ```json { "chapter_id": "integer (required) — The chapter whose reading order was reviewed", "confirmed": "boolean (required) — true after verifying the order; false to revoke a previous confirmation" } ``` **Example:** `confirm-page-order-tool { "chapter_id": 12, "confirmed": true }` --- ### Publish #### Tool: `publish-chapter-tool` Publish a chapter for a series — makes it publicly visible. Required ability: `publish:write`. Chapters typically contain 8-24 sequential pages telling one complete sub-story. Generate every page with `generate-pages-tool`, approve them with `approve-pages-tool` / `batch-approve-pages-tool`, then publish once. Publishing a one-page chapter is almost always a mistake (omake and splash chapters are the exception). **Input:** ```json { "series_id": "integer (required)", "title": "string (required) — Chapter title", "content": "string (optional) — Chapter synopsis; becomes the public teaser and meta description", "summary": "string (optional) — alias for content", "position": "number (optional) — Chapter number within the series, e.g. 1, 1.5, 2. Auto-increments if omitted", "status": "string (optional) — draft | published. Defaults to draft", "supporter_only": "boolean (optional) — Reserve the chapter for the creator's supporters. Defaults to false (free for everyone). Credits never unlock a chapter", "tags": "string[] (optional)" } ``` **Output:** ```json { "chapter_id": 99, "series_id": 42, "number": 1.0, "title": "The Beginning", "approval_status": "approved", "is_published": true, "page_count": 12, "warnings": [] } ``` The response includes a `warnings` array. If the chapter has fewer than 5 pages, the warning explains that manga chapters typically contain 8-24 sequential pages and suggests adding more pages before publishing — or ignoring the warning if it's a splash or omake. #### Tool: `submit-for-review-tool` Submit a chapter for moderation review. Required ability: `publish:write`. **Input:** `{ "chapter_id": "integer (required)" }` **Output:** ```json { "chapter_id": 99, "approval_status": "pending_review", "submitted": true } ``` #### Tool: `check-approval-status-tool` Check the current approval status of a chapter. Required ability: `publish:write`. **Input:** `{ "chapter_id": "integer (required)" }` **Output:** Chapter ID and current approval status. #### Tool: `unlock-chapter-for-edits-tool` Make a chapter editable again so its pages can be changed, lettered, reordered or replaced. Required ability: `publish:write`. **Publishing on Inkroost is not permanent.** A live chapter's pages are locked only while it is live; a chapter waiting for review only while a moderator holds it. This tool takes a live chapter offline (`take_offline`) or withdraws a pending one from the queue (`withdraw_from_review`), whichever the chapter needs. Never create a new chapter as a substitute for changing an existing one. It takes the work away from readers, or out of the review queue, so it is the human's decision: ask them, then pass `confirmed: true`. Calling it on a chapter that is already editable changes nothing and returns `already_editable: true`, so a repeated call is safe. Nothing is deleted, and **this tool never republishes**. Because the pages then change, the next `publish-chapter-tool` call goes through review again. An administrative hold on the series is the one lock it cannot lift; that is refused with a message naming it. **Input:** ```json { "chapter_id": "integer (required)", "confirmed": "boolean (required) — true only after the human agreed" } ``` **Output:** ```json { "chapter_id": 99, "series_id": 42, "number": 63.0, "state": "offline_was_live", "previous_state": "live", "approval_status": "approved", "is_published": false, "editable": true, "already_editable": false, "route": "take_offline", "page_count": 18, "last_page_id": 4471, "last_page_number": 18, "message": "Chapter 63 is offline. Readers can no longer open it; nothing is deleted. Publish again puts it back.", "republish_note": "The chapter stays offline. Publishing it again sends the changed pages through review..." } ``` `last_page_id` is there so an edit that belongs at the end of the chapter — a next-chapter teaser, for instance — lands on the page the human meant rather than one guessed at after the state change. --- ### Credits #### Tool: `check-balance-tool` Returns the current credit balance for the authenticated user. No input required. Required ability: `credits:read`. **Output:** ```json { "balance": 50, "currency": "credits" } ``` #### Tool: `estimate-generation-cost-tool` Estimate the credit cost for an AI generation operation before committing. Required ability: `credits:read`. **Input:** ```json { "page_count": "integer (required, 1-1000) — Number of pages to estimate the cost for" } ``` **Output:** ```json { "page_count": 5, "cost_per_page": 10, "total_cost": 50, "currency": "credits" } ``` #### Tool: `list-credit-packages-tool` List all available credit packages with their prices and IDs, for when the user asks what credits cost. Purchasing is browser-only and never happens through MCP; do not offer packages the user did not ask about. Required ability: `credits:read`. No input required. **Output:** ```json { "packages": [ { "id": 1, "name": "Starter", "credits": 100, "bonus_credits": 0, "total_credits": 100, "price_formatted": "$4.99", "price_cents": 499 }, { "id": 2, "name": "Creator", "credits": 500, "bonus_credits": 50, "total_credits": 550, "price_formatted": "$19.99", "price_cents": 1999 } ], "currency": "usd" } ``` #### Buying credits (browser-only — no MCP tool) There is no MCP tool for purchasing credits. Buying credits happens exclusively in the Inkroost web app (Settings → Credits), where the user completes the checkout themselves. Credits are added to the wallet automatically after successful payment. **Agent workflow for low-credit scenario:** ``` 1. check-balance-tool → balance ÷ cost_per_page = pages the balance draws 2. Tell the user that number and offer to draw what it covers 3. A generation tool refused for credits → pass on its reply, which says how many images the balance still covers and where credits come from 4. List packages only if the user asks what credits cost 5. User adds credits in their browser; they appear in the wallet automatically 6. check-balance-tool → confirm balance updated 7. Resume the original task ``` ### Animated episodes A finished chapter becomes an animated episode: an anime- or cartoon-style video with voice acting, music and sound, which the creator can download or publish to their own YouTube channel. Every spend needs the creator's agreement to the exact number of credits, passed back as a confirmation; a different number is refused with the current one and nothing is charged. A creator's first animated episode is at the launch price while the offer runs, and the tools return a sentence to pass on whenever it applies. #### Tool: `show-animation-examples-tool` Free and read-only. Returns the example episodes (title, series, the chapter it was made from, look, summary, duration, video and poster URLs, a link to read the comic), how a chapter becomes an episode, the Inkroost Animate page, and the launch offer for this account. Call it first when someone asks how to make an anime, a cartoon or an animated video. #### Tool: `estimate-episode-tool` Read-only. Input: `chapter_id` (required), `target_seconds`, `fit` (`whole` squeezes every page into a shorter length, `highlights` keeps the strongest scenes), `quality` (`draft`, `standard`, `premium`). Returns the chapter's natural length and the lengths it allows, `plan_credits`, `estimated_total_credits`, `usual_total_credits`, the launch offer, the balance and whether it covers the plan. #### Tool: `plan-episode-tool` Charges the plan step. Input as `estimate-episode-tool` plus `confirm_plan_credits` (required, must equal the current `plan_credits`). Returns `episode_id`, `status`, `plan_credits_charged`, `launch_offer` and the episode page. Planning takes about 3 to 10 minutes. #### Tool: `get-episode-tool` Read-only. Input: `episode_id`. Returns status and progress; while it waits for review, the cast with their voices, every scene with the lines heard, `total_credits`, `due_credits` and the launch offer; when ready, the video and poster URLs. #### Tool: `approve-episode-tool` Charges the rest of the price. Input: `episode_id`, `confirm_credits` (required, must equal `due_credits`), optional `voices` (`[{character_id, voice_id}]`, voices listed by `get-episode-tool`). Animation takes about 1 to 3 hours and the creator is emailed when it is ready. ### Studio app Clients that render MCP Apps (ChatGPT, Claude) show these tools as an interactive screen, the resource `ui://inkroost/studio`. Every other client receives the same structured JSON. What the creator does in the app reaches the assistant as an ordinary chat message (for example "Let's draw the next page of …" or "Let's use the Noir look (style preset noir).") or as model context naming the page they selected, so carry on from those as usual. Nothing in the app buys credits. #### Tool: `open-studio-tool` Opens the studio on the creator's library, or on a series or chapter when `series_id` or `chapter_id` is passed. In ChatGPT it is the "Inkroost" entry in the sidebar. Returns `view`, `series_id`, `chapter_id` and a short `message`. #### Tool: `open-chapter-panel-tool` Opens one chapter beside the conversation, so the creator watches its pages arrive while the conversation goes on. `chapter_id` is optional and defaults to the chapter the creator worked on last. Call it right after starting to draw a chapter's pages. #### Tool: `show-looks-tool` Shows a sample of every look, the same scene drawn in each, so the creator picks by sight. Pass `series_id` when the look is for an existing series. Choosing changes nothing: the choice arrives as a chat message with the `style_preset` id, which then goes to `set-series-style-profile-tool`. #### Generation tools render the same app `generate-pages-tool`, `generate-pages-batch-tool` and `regenerate-page-tool` declare the studio app as their output template. In ChatGPT and Claude the reply becomes a card that follows the job and shows the drawing the moment it is ready, with a button that keeps it (it calls `approve-pages-tool`). #### App-only tools `studio-library-tool`, `studio-series-tool`, `studio-chapter-tool` and `studio-jobs-tool` are the app's own reads, and `search-mentions-tool` fills ChatGPT's @-mention picker with the creator's series (`series://{id}`) and characters (`character://{seriesId}/{characterId}`). All five are marked `ui.visibility: ["app"]`, so the model never sees them. Images in their replies are signed links that expire within two hours. ### Settings ChatGPT shows these on the plugin's settings page; any assistant may use them when the creator asks. #### Tool: `get-settings-tool` Returns `schema` (JSON Schema of every setting), `values` and `layout`. The settings are the interface `language` (by its own name, e.g. "日本語") and the emails: `email_new_follower`, `email_new_chapter`, `email_review_result`, `email_comment_replies`, `email_comments_digest`, `email_creator_tips`. #### Tool: `update-settings-tool` Takes `set`, an object holding only what changes, for example `{"set": {"email_new_follower": false}}`. Every setting in the call is saved or none is. Returns every value after the change. Needs `story:write` on a manual token. --- ## Typical Agent Workflow A complete agent workflow for creating and publishing a chapter (comic, webtoon, or manga): 1. **Check balance** (`credits:read`) — Work out how many pages the balance draws (balance ÷ `cost_per_page` from `estimate-generation-cost-tool`) and size the plan to it. Purchasing is browser-only; list packages only if the user asks. 2. **Check daily limit** (`art:generate`) — Call `get-daily-limit-status-tool` to confirm you have enough remaining generations for your planned batch. Daily limit: 500/day, resets at midnight UTC. 3. **Create series** (`story:write`) — Call `create-series-tool` with title and metadata. 4. **Set the series style profile** (`story:write`) — Immediately call `set-series-style-profile-tool` with a detailed default profile (subject, photography, background, constraints). This is the single biggest lever for visual consistency across pages. 5. **Create characters** (`story:write`) — For each named character: `create-character-tool` → `set-character-style-profile-tool` with detailed visual anchors (hair length + colour + parting, eye colour + shape, distinguishing marks, default outfit, build). 5b. **Create prop sheets** (`story:write`) — For each recurring object, above all any phone or handheld device: `create-prop-tool` with concrete `appearance_notes`, a shape anchor (`looks_like` or `real_world_model_anchor`), and, in `aliases`, the words your beats will actually use for it. Objects drift exactly the way faces do — and the anchor is what stops the SMALL detail drifting once the object itself is right. 5c. **Create location sheets** (`story:write`) — For each recurring place, above all the one your chapters keep opening on: `create-location-tool` with `appearance_notes` written as an architect's spec (storeys and approximate measurements, geometry, materials, window and door layout as counts and positions, the exact wording on any sign, and a named real-world architectural model or typology where the place has one) and, in `aliases`, the words your beats will actually use for it. Space drifts the way faces and objects do, and readers notice a different building as fast as a different face. 6. **Inspect consistency context** (`story:read`) — Call `get-consistency-context-tool` to review the series style profile, character IDs, prop IDs, location IDs, shape anchors and completeness scores. Fill any gaps before generating — including props whose `completeness_score.has_anchor` is false. 7. **Create a chapter shell** (`story:write`) — Call `create-chapter-tool`. A chapter is the container for multiple sequential pages. 8. **Plan the chapter** — A manga chapter typically runs 8-24 sequential pages telling one complete sub-story. When the series has no drawn page yet, draw page 1 first (one image, `variant_count: 1`) and show it to the user before planning the rest: it is how they see whether the look is right. A page takes about 2 minutes. 9. **Generate pages** (`art:generate`) — For a single beat use `generate-pages-tool`; for multiple beats at once use `generate-pages-batch-tool` (up to 20 prompts per call). Pass `character_ids` so the server injects the right style profiles automatically, `prop_ids` for any recurring object in the panel, and `location_ids` for the place the beat is set in (props and locations named in the scene text are injected automatically as well). Leave `allow_text=false` (default) for clean art; add dialog in step 12 instead. 10. **Poll status** (`art:generate`) — Use `get-art-status-tool` until each job's status is `review`. For failed jobs, `error_message` contains the reason. 11. **Analyse generated images** — When the job reaches `review` status, call `get-page-image-tool` with the `job_id` to pull the actual variant images back and **look at them** (it returns the bytes as viewable image content, so no separate URL fetch is needed). `get-art-status-tool` still returns `result_urls` if you prefer the links. **Visually analyse each variant before approving:** - Are all characters recognisable and consistent with their style profiles? - Does the scene match the intended composition and action? - Is the panel layout, lighting, and mood correct? - If `allow_text=true` was used, is the dialog readable, correctly attributed, and free of spelling errors? **If the image does not meet expectations**, call `approve-pages-tool` with `discard: true` to reject it (credits are not refunded), then regenerate with a refined prompt. Common refinements: - Add more specific physical descriptors to the prompt - Reference the character's style profile fields explicitly in the scene description - Add composition hints: "close-up on face", "wide shot from above", "three-quarter profile" - Simplify complex multi-character scenes into separate single-character pages 12. **Approve pages** (`art:generate`) — Once satisfied with the image, use `approve-pages-tool` for a single job or `batch-approve-pages-tool` for multiple jobs at once. Both create Page records autonomously. Use `variant_indices` to select specific variants or omit to approve all. 13. **Annotate (optional)** (`art:generate`) — For in-image text, prefer re-generating with `generate-pages-tool` using `style_prompt` and `allow_text=true` so the model places text context-aware. If post-generation annotation is needed, use `annotate-page-tool`. 14. **Write the text layer** (`story:write`) — Call `set-page-text-tool` for every approved page with an `alt_text` and a full `transcript`, and give the chapter a synopsis via `update-chapter-tool`. Skipping this step is the single most common reason a finished chapter never surfaces in search or in an AI assistant's answer: the artwork carries no machine-readable text of its own. **Repairing an existing library.** Chapters published before the text layer existed hold every image and no text at all, which makes them invisible to screen readers, search engines and AI assistants. The loop is three calls: `get-chapters-tool { "needs_text_version": true }` with no `series_id` returns the whole repair queue, most incomplete first; `get-pages-tool` on a chapter returns `pages_missing_text_version`; `set-page-text-tool` with a `pages` array fixes that chapter in one write. Ground each description in the page's own image and `ai_prompt` — never keyword-stuff, and never describe something the artwork does not show. 15. **Confirm the reading order** (`story:write`) — Call `confirm-page-order-tool` once you have verified the pages read in the intended narrative order. 16. **Submit once** (`publish:write`) — Once every page is approved, call either `publish-chapter-tool` or `submit-for-review-tool`; both begin moderation, so do not call both for the same chapter. The `publish-chapter-tool` response also flags chapters with fewer than 5 pages. 17. **Changing a chapter afterwards** (`publish:write`) — To edit a chapter that is already live or in review, call `unlock-chapter-for-edits-tool` on that chapter with the human's `confirmed: true`, make the edit on the same chapter and page, and leave it offline unless they ask you to publish it again. Never start a new chapter as a substitute. 17. **Check approval when requested** (`publish:write`) — Poll `check-approval-status-tool` only when the user asked you to monitor moderation; otherwise report the pending status and stop. --- ## Prompting Best Practices ### Character Consistency AI image models have no persistent memory between jobs. The server helps by auto-injecting consistency context into every generation prompt: - **Series default style profile** — when `style_profile` is omitted from `generate-pages-tool` or `generate-pages-batch-tool`, the series `default_style_profile` is used automatically. - **Character injection** — when you pass `character_ids`, the server merges each character's `appearance_notes` and `style_profile` into the prompt in a stable order before your scene description. - **Location injection** — every location sheet whose `location_id` you pass, or whose name or alias appears in the scene text, is injected in a stable order as a set of reproduction constraints. Where a location carries a canonical reference image and anchoring is on, establishing and wide shots are also conditioned on that image. - **Prop injection** — the same, for objects: every prop sheet whose `prop_id` you pass, or whose name or alias appears in the scene text, is injected in a stable order, shape anchor included. Where a prop carries a canonical reference picture, close-up, macro, insert and detail panels are also conditioned on that image. You do not have to pass `prop_ids` for this to happen, and on a chapter's worth of beats you will not remember to. - **The previous page** — pages queued together as one run (`generate-pages-batch-tool`) are drawn in order, and each page after the first carries the finished drawing of the page before it as a reference image, beside the sheets rather than instead of them. It is the strongest continuity signal there is, and it is why a batch holds together where the same beats queued one at a time drift. A page queued on its own with `generate-pages-tool` belongs to no run and has no previous page attached. **Always pass `character_ids`** for scenes with named characters — this is the most reliable way to keep characters consistent across pages without repeating their descriptions manually. Call `get-consistency-context-tool` before generating to preview what will be injected and to check completeness scores. Characters below 70% completeness may produce inconsistent results; fill in missing fields with `set-character-style-profile-tool` first. For scenes where a character has critical visual details not yet in their profile, you can still embed them inline in `prompt` or `continuity_notes`: ``` continuity_notes: "Kira still has the bandage on her left arm from Chapter 2. The amulet glows red, not blue." ``` Never rely on a character's name alone to anchor appearance — image models do not know your characters, only what is described in the prompt. ### Iterative Refinement Loop High-quality manga pages rarely emerge from a single generation. Treat generation as a loop: ``` 1. generate-pages-tool / generate-pages-batch-tool → job queued 2. get-art-status-tool (poll until status = "review") 3. get-page-image-tool → view each variant image and analyse it 4a. Image OK → approve-pages-tool → continue 4b. Image not OK → approve-pages-tool (discard: true) → refine prompt → go to step 1 ``` Common reasons to discard and retry: - Character appearance doesn't match the style profile (wrong hair, eye colour, etc.) - Wrong number of characters in the scene - Composition is too cluttered or key action is unclear - Dialog text (when `allow_text=true`) is garbled or unreadable Each retry consumes credits, so invest in precise style profiles and prompts upfront to reduce iteration cycles. ### Scene Prompting Tips - **One idea per panel** — describe one action, emotion, or moment per page generation; complex multi-event scenes almost never render well - **Foreground vs. background** — explicitly separate foreground subjects from background: "Ren stands in the foreground, sword raised; behind him, burning ruins and scattered soldiers" - **Camera angle and framing** — name it: "extreme close-up on eyes", "low-angle wide shot", "over-the-shoulder from behind Kira" - **Lighting and mood** — specify: "harsh neon back-lighting", "soft dawn light", "dim torch-lit cave" - **Panel context** — if the page follows a specific moment in the story, briefly name it: "aftermath of the fight — Kira kneels beside the fallen enemy, exhausted" --- ## Daily AI Generation Limit Each user account may generate up to **500 AI images per day**. This counter is shared across all generation tools (`generate-pages-tool`, `generate-pages-batch-tool`, `generate-cover-tool`, `generate-banner-tool`). The counter resets at midnight UTC. - Use `get-daily-limit-status-tool` to check `used_today`, `remaining`, and `resets_at` before queuing batches. - When the limit is reached, generation tools return: `"Daily AI generation limit reached (500/day). Your limit resets at midnight UTC."` - For very large projects (e.g. 1000+ pages), spread generation across multiple days. All AI generation consumes Inkroost Credits — when the balance runs low, the user adds credits in the Inkroost web app (purchasing is browser-only). ## Rate Limiting All MCP endpoints are rate-limited to 60 requests per minute. Credits-consuming operations (art generation) require a sufficient wallet balance — insufficient credits return an error response. --- ## Publishing to External Platforms Inkroost work is portable. A creator (or their agent, via the creator) can publish a series or a single chapter to an external AI-comic generating-and-distribution platform that has integrated with Inkroost: - The creator connects their account to the platform once, via a standard OAuth2 authorization-code flow with a stable return/callback URL (`https://inkroost.com/integrations/callback`). Tokens are stored encrypted per user per platform, with a disconnect path that deletes them. - "Publish to " on the publish desk in the studio (`https://inkroost.com/creator`) packages the work — `manifest.json`, the cover, and every page image under zero-padded `chapter-XXX/page-XXX` names so lexical order is reading order — and sends it to the platform in the background, retrying on transient failures and reporting success or failure in the studio. - Two delivery modes, chosen per partner: a single multipart POST to the partner's ingest endpoint, or a signed-URL flow (small manifest request → direct ZIP PUT to a short-lived signed upload URL → status polling until published/failed) for partners whose ingest cannot accept large request bodies. - The manifest carries the metadata an integrating platform needs: title, synopsis, reading direction (ltr/rtl), reading mode (paged/vertical), maturity rating, genres, content origin, creator attribution, canonical source URLs, stable series and chapter ids for cross-publish continuity, and the source language. The language is always present: `source_language` is the ISO 639-1 code (`en` unless the creator chose otherwise) and a `language` object repeats it as `iso_639_1`, `iso_639_2`, `bcp47` and the English `name`, so a partner never has to map ours onto its own format. - Page art ships as the original PNG/JPEG files; width typically 800-2048px, height unbounded (vertical-scroll pages), at most 500 pages per publish. - The creator is notified in-app on success (with a link to the published item when the platform returns one) and on failure. The complete partner contract — endpoint expectations, request/response shapes, asset technical specifications and limits — is the Publish/Export API document: https://inkroost.com/docs/publish-api ## Resources - [Machine-readable summary](https://inkroost.com/llms.txt): the short form of this file. - [AI agent access](https://inkroost.com/settings/api-tokens): issue and revoke bearer tokens. - [Pricing and credits](https://inkroost.com/pricing): packages and per-generation cost. - [Support](https://inkroost.com/support): contact Inkroost and learn what information to include in a support request.