The Prosie API (for power users)
Prosie exposes a token-authenticated HTTP API so you can drive your stories from your own tools — a backup script, an Obsidian plugin, an iOS Shortcut, a Discord bot, or a keyboard macro. The API carries the same voice-preserving context as the web app (your prose style, lore, characters and story-so-far), so a continuation generated over the API reads just like one generated in the editor.
It's a small, predictable REST API: JSON in, JSON out for application resources, streamed downloads for exports, one resource per URL. Everything below is scoped to the token owner — you can only ever see and touch your own work.
Using the official Prosie CLI
jonbaldie/prosie-cli
is the official command-line client for Prosie's headless API. Use it from
your terminal, scripts, or coding agent to manage books and chapters,
export prose, and generate continuations. It connects to
https://prosie.app
by default and handles API requests and authentication for you.
Install and sign in
brew install jonbaldie/tap/prosie
prosie auth login
prosie auth status
The login command opens your browser to approve access and saves the login
locally. For read-only access, use prosie auth login --scopes read.
Without Homebrew, download a binary from the
GitHub releases.
For scripts, you can supply an API token through the
PROSIE_API_TOKEN
environment variable.
Read and export your work
Start with the book list, then use the returned IDs. Replace
BOOK_ID and
CHAPTER_ID below with your own IDs.
The CLI calls them books and chapters; the HTTP API below calls them stories and scenes.
prosie book list
prosie book show BOOK_ID
prosie chapter list BOOK_ID
prosie chapter show CHAPTER_ID
prosie book export BOOK_ID --output manuscript.md
prosie book list --json
Use --json for structured output in scripts and coding agents.
Run prosie --help for commands covering series, codex entries, chat, and more.
Create a book or generate prose
prosie book create --title "My Novel"
prosie generate continue CHAPTER_ID --no-persist --json
Creating a book needs write permission.
Generation needs generate permission and configured LLM settings;
it uses your LLM credits. The example returns a continuation without saving it.
Omit --no-persist to append it to the chapter, which also needs
write permission.
Using Prosie from a coding agent
Want a coding agent (Codex, Claude Code, …) to drive Prosie for you? Create a token, then hand the agent the copyable block below. It carries everything the agent needs — base URL, headers, what each permission means, and the safety rules to follow.
1. Create a token
Open API Tokens
from the account menu (top-right) — it's an account
setting, not a per-story one. Give the token a name that says what it's for
(e.g. claude-code-readonly)
so you recognise it later.
2. Pick the least permission it needs
read— the agent can list and inspect your stories, scenes, series, codex entries and exports, but change nothing. Safest starting point.write— the agent can create, import, update, reorder and delete story data. Grant only when you want it editing.generate— the agent can run AI continuations, rewrites and summaries. This spends your LLM credits.
Recipes: read-only inspection → read;
importing or editing books → read + write;
letting it generate prose → add generate.
Prefer the smallest set — you can always mint another token later.
Every headless API route is limited to 60 requests per minute per account.
Over that, the API returns 429. Generation calls also have a tighter limit of 30 per minute.
3. Keep the token secret
Prosie shows the token's value once, at creation — copy it
then. Never commit it to a repository or paste it into project files. Give it to
your agent through a secure local mechanism instead: an environment variable, a
secret store, or a private prompt. The placeholder below is
<YOUR_PROSIE_API_TOKEN> —
replace it with your real token only in that secure place.
4. Copy the agent instructions
You can drive my Prosie account through its headless REST API. Follow these rules.
## Base URL
https://prosie.app/api
## Authentication
Send my token as a bearer header on every request. The token value will be provided
to you securely (e.g. an environment variable) — never hard-code or commit it.
Authorization: Bearer <YOUR_PROSIE_API_TOKEN>
Accept: application/json
Content-Type: application/json
Everything is scoped to the token's owner: you can only see and touch my own work.
## Token abilities (permissions)
Every operation in the OpenAPI spec below carries an `x-ability` field naming the
ability it needs: `read`, `write`, or `generate` (running AI continuations, rewrites
and summaries — this spends my LLM credits). If a request returns 403, the token
most likely lacks that ability. Report that clearly ("this needs a token with the
`write` ability") instead of failing silently. Every headless API route is limited to 60 requests per minute per account (429 if exceeded). Generation endpoints are also rate
limited (429 if exceeded, default 30/minute per account).
## Safety rules
- Ask me before any destructive write (delete, overwrite, bulk reorder).
- Do NOT call `x-ability: generate` endpoints unless I explicitly ask — they cost money.
- Prefer read calls to understand state before changing anything.
- On 422, read the `errors` object — it names the invalid field(s).
## First smoke test (harmless read — needs the `read` ability)
curl https://prosie.app/api/stories \
-H "Authorization: Bearer <YOUR_PROSIE_API_TOKEN>" \
-H "Accept: application/json"
If that returns a JSON list of my stories, the token works.
## Full API spec (OpenAPI 3.1)
Below is the complete, authoritative spec for every endpoint — paths, methods,
request/response schemas and the `x-ability` each one needs. Treat it as ground
truth over anything else you've inferred.
{
"openapi": "3.1.0",
"info": {
"title": "Prosie API",
"version": "1.0.0",
"description": "Headless REST API for Prosie. Every route is scoped to the Sanctum token owner and gated on a token ability (read/write/generate). Every route is rate limited to 60 requests per minute per account. Generation routes have an additional limit of 30 requests per minute per account."
},
"servers": [
{ "url": "https://prosie.app/api" }
],
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"description": "Sanctum personal access token."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"message": { "type": "string" },
"errors": {
"type": "object",
"additionalProperties": { "type": "array", "items": { "type": "string" } },
"description": "Present on 422 validation failures only, keyed by field."
}
},
"required": ["message"]
},
"Story": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"title": { "type": "string" },
"subtitle": { "type": ["string", "null"] },
"series_id": { "type": ["integer", "null"] },
"pov": { "type": "string", "enum": ["first", "second", "third"] },
"tense": { "type": "string", "enum": ["past", "present", "future"] },
"lore": { "type": ["string", "null"] },
"characters": { "type": ["string", "null"] },
"story_so_far": { "type": ["string", "null"] },
"prose_style": { "type": ["string", "null"] },
"target_word_count": { "type": ["integer", "null"] },
"filter_using_story_so_far": { "type": "boolean" },
"chapters": { "type": "array", "items": { "$ref": "#/components/schemas/Scene" }, "description": "Ordered chapters, included when creating or reading a single book." },
"word_count": { "type": "integer" },
"target_progress_percent": { "type": ["integer", "null"] },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"Scene": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"story_id": { "type": "integer" },
"order": { "type": "integer" },
"name": { "type": ["string", "null"] },
"custom_pov": { "type": ["string", "null"] },
"custom_character": { "type": ["string", "null"] },
"content": { "type": ["string", "null"], "description": "HTML." },
"summary": { "type": ["string", "null"] },
"word_count": { "type": "integer" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"Series": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"description": { "type": ["string", "null"] },
"story_ids": { "type": "array", "items": { "type": "integer" } },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"CodexEntry": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"story_id": { "type": ["integer", "null"] },
"series_id": { "type": ["integer", "null"] },
"parent_id": { "type": ["integer", "null"], "description": "Set when this is a per-book append to a series-shared entry." },
"category": { "type": "string", "enum": ["lore", "character"] },
"name": { "type": "string" },
"aliases": { "type": ["string", "null"] },
"content": { "type": "string" },
"order": { "type": "integer" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"RewritePrompt": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"key": { "type": "string", "description": "Pass this as the rewrite endpoint's `action` field." },
"label": { "type": "string" },
"instruction": { "type": "string" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"RewriteAction": {
"type": "object",
"properties": {
"key": { "type": "string" },
"label": { "type": "string" },
"instruction": { "type": "string" }
}
},
"LlmConfig": {
"type": "object",
"properties": {
"provider": { "type": ["string", "null"], "enum": ["openai", "anthropic", "openrouter", null] },
"model": { "type": ["string", "null"] },
"has_openai_key": { "type": "boolean" },
"has_anthropic_key": { "type": "boolean" },
"has_openrouter_key": { "type": "boolean" }
}
},
"GenerationUsage": {
"type": "object",
"properties": {
"prompt_tokens": { "type": "integer" },
"completion_tokens": { "type": "integer" },
"total_tokens": { "type": "integer" }
}
},
"ContinueResult": {
"type": "object",
"properties": {
"prose": { "type": "string" },
"html": { "type": "string" },
"persisted": { "type": "boolean" },
"model": { "type": "string" },
"usage": { "$ref": "#/components/schemas/GenerationUsage" },
"estimated_prompt_tokens": { "type": "integer" }
}
},
"RewriteResult": {
"type": "object",
"properties": {
"prose": { "type": "string" },
"model": { "type": "string" },
"usage": { "$ref": "#/components/schemas/GenerationUsage" },
"estimated_prompt_tokens": { "type": "integer" }
}
},
"SummaryResult": {
"type": "object",
"properties": {
"summary": { "type": "string" },
"persisted": { "type": "boolean" },
"model": { "type": "string" },
"usage": { "$ref": "#/components/schemas/GenerationUsage" },
"estimated_prompt_tokens": { "type": "integer" }
}
},
"ChatMessage": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"conversation_id": { "type": "integer" },
"role": { "type": "string", "enum": ["user", "assistant"] },
"content": { "type": "string" },
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
},
"Conversation": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"story_id": { "type": "integer" },
"title": { "type": ["string", "null"] },
"model": { "type": ["string", "null"] },
"up_to_scene_id": { "type": ["integer", "null"] },
"fidelity": { "type": "string", "enum": ["summary", "verbatim"] },
"expires_in_days": { "type": "integer" },
"messages": {
"type": "array",
"items": { "$ref": "#/components/schemas/ChatMessage" }
},
"created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }
}
}
},
"responses": {
"ValidationError": {
"description": "422 Unprocessable Content - request failed validation.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"Forbidden": {
"description": "403 Forbidden - the token lacks the required ability, or the resource belongs to another account.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"NotFound": {
"description": "404 Not Found.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"TooManyRequests": {
"description": "429 Too Many Requests - the account's generation rate limit (default 30/min) was exceeded.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"UpstreamGenerationError": {
"description": "502 Bad Gateway - the upstream LLM provider failed.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
}
}
},
"security": [{ "bearerAuth": [] }],
"paths": {
"/stories": {
"get": {
"summary": "List your stories",
"x-ability": "read",
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Story" } } } } } } }
}
},
"post": {
"summary": "Create a story",
"x-ability": "write",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["title"],
"properties": {
"title": { "type": "string", "maxLength": 255 },
"subtitle": { "type": ["string", "null"], "maxLength": 255 },
"series_id": { "type": ["integer", "null"], "description": "Must be a series you own." },
"pov": { "type": "string", "enum": ["first", "second", "third"], "default": "third" },
"tense": { "type": "string", "enum": ["past", "present", "future"], "default": "past" },
"lore": { "type": ["string", "null"] },
"characters": { "type": ["string", "null"] },
"story_so_far": { "type": ["string", "null"] },
"prose_style": { "type": ["string", "null"] },
"target_word_count": { "type": ["integer", "null"], "minimum": 1, "maximum": 100000000 },
"filter_using_story_so_far": { "type": "boolean" },
"scaffold": { "type": "boolean", "default": true, "description": "false skips the default 'Chapter 1' scene." }
}
}
}
}
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Story" } } } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/stories/import-docx": {
"post": {
"summary": "Import a DOCX story, using Heading 1 paragraphs as scene boundaries",
"x-ability": "write",
"requestBody": {
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": ["document", "title"],
"properties": {
"document": { "type": "string", "format": "binary", "description": ".docx file." },
"title": { "type": "string", "maxLength": 255 },
"subtitle": { "type": ["string", "null"] },
"series_id": { "type": ["integer", "null"] },
"pov": { "type": "string", "enum": ["first", "second", "third"] },
"tense": { "type": "string", "enum": ["past", "present", "future"] },
"lore": { "type": ["string", "null"] },
"characters": { "type": ["string", "null"] },
"story_so_far": { "type": ["string", "null"] },
"prose_style": { "type": ["string", "null"] },
"target_word_count": { "type": ["integer", "null"] },
"filter_using_story_so_far": { "type": "boolean" }
}
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": { "application/json": { "schema": { "type": "object", "properties": {
"data": { "$ref": "#/components/schemas/Story" },
"scenes": { "type": "array", "items": { "$ref": "#/components/schemas/Scene" } }
} } } }
},
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/stories/{story}": {
"get": {
"summary": "Fetch one story",
"x-ability": "read",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Story" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
},
"patch": {
"summary": "Update any story field",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "description": "All fields optional; see POST /stories for the field list." } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Story" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" }
}
},
"delete": {
"summary": "Delete a story",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"204": { "description": "No Content" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/stories/{story}/duplicate": {
"post": {
"summary": "Duplicate an owned story with its scenes and codex entries",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Story" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
}
},
"/series": {
"get": {
"summary": "List your series",
"x-ability": "read",
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Series" } } } } } } }
}
},
"post": {
"summary": "Create a series",
"x-ability": "write",
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["name"], "properties": {
"name": { "type": "string", "maxLength": 255 },
"description": { "type": ["string", "null"], "maxLength": 5000 }
} } } }
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Series" } } } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/series/{series}": {
"get": {
"summary": "Fetch one series, including its member book IDs",
"x-ability": "read",
"parameters": [{ "name": "series", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Series" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
},
"patch": {
"summary": "Update a series",
"x-ability": "write",
"parameters": [{ "name": "series", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"name": { "type": "string", "maxLength": 255 },
"description": { "type": ["string", "null"], "maxLength": 5000 }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Series" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" }
}
},
"delete": {
"summary": "Delete a series",
"x-ability": "write",
"parameters": [{ "name": "series", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"204": { "description": "No Content" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/series/{series}/stories/{story}": {
"put": {
"summary": "Attach one of your books to a series",
"x-ability": "write",
"parameters": [
{ "name": "series", "in": "path", "required": true, "schema": { "type": "integer" } },
{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }
],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Story" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
},
"delete": {
"summary": "Detach a member book from a series",
"x-ability": "write",
"parameters": [
{ "name": "series", "in": "path", "required": true, "schema": { "type": "integer" } },
{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }
],
"responses": {
"204": { "description": "No Content" },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
}
},
"/series/{series}/codex-entries": {
"get": {
"summary": "List a series' shared base codex entries",
"x-ability": "read",
"parameters": [{ "name": "series", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/CodexEntry" } } } } } } }
}
},
"post": {
"summary": "Add a shared base codex entry to a series",
"x-ability": "write",
"parameters": [{ "name": "series", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["category", "name", "content"], "properties": {
"category": { "type": "string", "enum": ["lore", "character"] },
"name": { "type": "string", "maxLength": 120 },
"aliases": { "type": ["string", "null"], "maxLength": 500 },
"content": { "type": "string", "maxLength": 50000 },
"order": { "type": "integer", "minimum": 0 }
} } } }
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CodexEntry" } } } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/series-codex-entries/{codexEntry}": {
"patch": {
"summary": "Update a shared base codex entry",
"x-ability": "write",
"parameters": [{ "name": "codexEntry", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"category": { "type": "string", "enum": ["lore", "character"] },
"name": { "type": "string", "maxLength": 120 },
"aliases": { "type": ["string", "null"], "maxLength": 500 },
"content": { "type": "string", "maxLength": 50000 },
"order": { "type": "integer", "minimum": 0 }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CodexEntry" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
},
"delete": {
"summary": "Delete a shared base codex entry",
"x-ability": "write",
"parameters": [{ "name": "codexEntry", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"204": { "description": "No Content" },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
}
},
"/stories/{story}/series-codex-entries/{codexEntry}/append": {
"put": {
"summary": "Create or update this book's append to a shared series codex entry",
"x-ability": "write",
"parameters": [
{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } },
{ "name": "codexEntry", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "The base entry's id." }
],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["content"], "properties": {
"content": { "type": "string", "maxLength": 50000 }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CodexEntry" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "description": "The base entry isn't shared with this book's current series.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
},
"delete": {
"summary": "Delete this book's append to a shared series codex entry",
"x-ability": "write",
"parameters": [
{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } },
{ "name": "codexEntry", "in": "path", "required": true, "schema": { "type": "integer" } }
],
"responses": {
"204": { "description": "No Content" },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
}
},
"/stories/{story}/scenes": {
"get": {
"summary": "List a story's scenes, in order",
"x-ability": "read",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Scene" } } } } } } }
}
},
"post": {
"summary": "Add a scene, appended to the end unless order is given",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"name": { "type": ["string", "null"], "maxLength": 255 },
"order": { "type": "integer", "minimum": 0 },
"custom_pov": { "type": ["string", "null"], "maxLength": 255 },
"custom_character": { "type": ["string", "null"], "maxLength": 255 },
"content": { "type": ["string", "null"], "description": "HTML." },
"summary": { "type": ["string", "null"] }
} } } }
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Scene" } } } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/stories/{story}/scene-summaries": {
"patch": {
"summary": "Bulk update scene summaries for one story, atomically",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["summaries"], "properties": {
"summaries": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["scene_id", "summary"], "properties": {
"scene_id": { "type": "integer" },
"summary": { "type": ["string", "null"] }
} } }
} } } }
},
"responses": {
"200": { "description": "OK - every referenced scene belonged to this story", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Scene" } } } } } } },
"422": { "description": "A referenced scene_id does not belong to this story; nothing was changed.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/stories/{story}/scenes/reorder": {
"post": {
"summary": "Reorder a story's scenes from an ordered list of scene_ids",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["scene_ids"], "properties": {
"scene_ids": { "type": "array", "items": { "type": "integer" }, "description": "Ids not belonging to this story and repeated ids are ignored. Omitted chapters follow the requested chapters in their existing order; positions are contiguous from zero." }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Scene" } } } } } } }
}
}
},
"/stories/{story}/codex-entries": {
"get": {
"summary": "List a story's standalone codex entries",
"x-ability": "read",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/CodexEntry" } } } } } } }
}
},
"post": {
"summary": "Add a standalone codex entry to a story",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["category", "name", "content"], "properties": {
"category": { "type": "string", "enum": ["lore", "character"] },
"name": { "type": "string", "maxLength": 120 },
"aliases": { "type": ["string", "null"], "maxLength": 500 },
"content": { "type": "string", "maxLength": 50000 },
"order": { "type": "integer", "minimum": 0 }
} } } }
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CodexEntry" } } } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/stories/{story}/codex-entries/bulk": {
"post": {
"summary": "Create several standalone codex entries on a story in one call",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["entries"], "properties": {
"entries": { "type": "array", "minItems": 1, "maxItems": 100, "items": { "type": "object", "required": ["category", "name", "content"], "properties": {
"category": { "type": "string", "enum": ["lore", "character"] },
"name": { "type": "string", "maxLength": 120 },
"aliases": { "type": ["string", "null"], "maxLength": 500 },
"content": { "type": "string", "maxLength": 50000 },
"order": { "type": "integer", "minimum": 0 }
} } }
} } } }
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/CodexEntry" } } } } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/codex-entries/{codexEntry}": {
"get": {
"summary": "Fetch one standalone codex entry",
"x-ability": "read",
"parameters": [{ "name": "codexEntry", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CodexEntry" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
},
"patch": {
"summary": "Update a standalone codex entry",
"x-ability": "write",
"parameters": [{ "name": "codexEntry", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"category": { "type": "string", "enum": ["lore", "character"] },
"name": { "type": "string", "maxLength": 120 },
"aliases": { "type": ["string", "null"], "maxLength": 500 },
"content": { "type": "string", "maxLength": 50000 },
"order": { "type": "integer", "minimum": 0 }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CodexEntry" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" }
}
},
"delete": {
"summary": "Delete a standalone codex entry",
"x-ability": "write",
"parameters": [{ "name": "codexEntry", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"204": { "description": "No Content" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/scenes/{scene}": {
"get": {
"summary": "Fetch one scene",
"x-ability": "read",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Scene" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
},
"patch": {
"summary": "Update a scene's name, content, summary, POV or character",
"x-ability": "write",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"name": { "type": ["string", "null"], "maxLength": 255 },
"order": { "type": "integer", "minimum": 0 },
"custom_pov": { "type": ["string", "null"], "maxLength": 255 },
"custom_character": { "type": ["string", "null"], "maxLength": 255 },
"content": { "type": ["string", "null"] },
"summary": { "type": ["string", "null"] }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Scene" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" }
}
},
"delete": {
"summary": "Delete a scene. Refused if it's the story's last one",
"x-ability": "write",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"204": { "description": "No Content" },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "description": "A story must keep at least one scene.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
}
}
},
"/scenes/{scene}/export": {
"get": {
"summary": "Export one scene as Markdown or Word",
"x-ability": "read",
"parameters": [
{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } },
{ "name": "format", "in": "query", "required": false, "schema": { "type": "string", "enum": ["markdown", "docx"], "default": "markdown" } }
],
"responses": {
"200": { "description": "Streamed file download (text/markdown or .docx).", "content": { "text/markdown": {}, "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {} } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/stories/{story}/export": {
"get": {
"summary": "Export a whole story as Markdown or Word",
"x-ability": "read",
"parameters": [
{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } },
{ "name": "format", "in": "query", "required": false, "schema": { "type": "string", "enum": ["markdown", "docx"], "default": "markdown" } }
],
"responses": {
"200": { "description": "Streamed file download (text/markdown or .docx).", "content": { "text/markdown": {}, "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {} } },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
}
},
"/scenes/{scene}/reject-continuation": {
"post": {
"summary": "Drop the most recent AI continuation from a scene",
"x-ability": "write",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK - a no-op when the scene doesn't end in generated prose.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Scene" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/scenes/{scene}/rewrite/undo": {
"post": {
"summary": "Revert the most recent in-place rewrite on a scene",
"x-ability": "write",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK - reverts scene content to pre-rewrite baseline.", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Scene" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/llm-config": {
"get": {
"summary": "Read your provider, model and which keys are stored",
"x-ability": "read",
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/LlmConfig" } } } } } }
}
},
"patch": {
"summary": "Change provider, model or API keys (keys are write-only)",
"x-ability": "write",
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"provider": { "type": "string", "enum": ["openai", "anthropic", "openrouter"] },
"model": { "type": "string", "maxLength": 255 },
"openai_api_key": { "type": ["string", "null"], "description": "Omitted or empty keeps the currently stored key." },
"anthropic_api_key": { "type": ["string", "null"] },
"openrouter_api_key": { "type": ["string", "null"] }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/LlmConfig" } } } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/llm-models": {
"get": {
"summary": "The model catalogue per provider, plus which providers you've keyed",
"x-ability": "read",
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "object", "properties": {
"providers": { "type": "object", "description": "Model list keyed by provider." },
"configured": { "type": "array", "items": { "type": "string" }, "description": "Providers you hold an API key for." }
} } } } } } }
}
}
},
"/rewrite-prompts": {
"get": {
"summary": "List your own rewrite prompts",
"x-ability": "read",
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/RewritePrompt" } } } } } } }
}
},
"post": {
"summary": "Create a rewrite prompt",
"x-ability": "write",
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["label", "instruction"], "properties": {
"label": { "type": "string", "maxLength": 60 },
"instruction": { "type": "string", "maxLength": 2000 }
} } } }
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/RewritePrompt" } } } } } },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/rewrite-prompts/{id}": {
"get": {
"summary": "Fetch one rewrite prompt",
"x-ability": "read",
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/RewritePrompt" } } } } } },
"404": { "$ref": "#/components/responses/NotFound" }
}
},
"patch": {
"summary": "Update a rewrite prompt",
"x-ability": "write",
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"label": { "type": "string", "maxLength": 60 },
"instruction": { "type": "string", "maxLength": 2000 }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/RewritePrompt" } } } } } },
"404": { "$ref": "#/components/responses/NotFound" },
"422": { "$ref": "#/components/responses/ValidationError" }
}
},
"delete": {
"summary": "Delete a rewrite prompt",
"x-ability": "write",
"parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"204": { "description": "No Content" },
"404": { "$ref": "#/components/responses/NotFound" }
}
}
},
"/rewrite-actions": {
"get": {
"summary": "Every rewrite action (built-ins + yours), keyed for the rewrite endpoint's action field",
"x-ability": "read",
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/RewriteAction" } } } } } } }
}
}
},
"/story-templates": {
"get": {
"summary": "List story structure templates",
"x-ability": "read",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"description": { "type": "string" },
"beat_count": { "type": "integer" }
}
}
}
}
}
}
}
}
}
}
},
"/story-templates/{key}": {
"get": {
"summary": "Fetch one story structure template with its beats",
"x-ability": "read",
"parameters": [{ "name": "key", "in": "path", "required": true, "schema": { "type": "string" } }],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"description": { "type": "string" },
"beats": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"guidance": { "type": "string" }
}
}
}
}
}
}
}
}
}
},
"404": { "$ref": "#/components/responses/NotFound" }
}
}
},
"/scenes/{scene}/continue": {
"post": {
"summary": "Generate a continuation. Saves & appends by default (needs write too)",
"x-ability": "generate",
"description": "Rate-limited to 30 requests/minute per account (config prosie.llm_rate_limit).",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"instruction": { "type": ["string", "null"], "description": "Optional beat/steering instruction." },
"persist": { "type": "boolean", "default": true, "description": "false returns the prose without saving it (needs only generate, not write)." }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/ContinueResult" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"429": { "$ref": "#/components/responses/TooManyRequests" },
"502": { "$ref": "#/components/responses/UpstreamGenerationError" }
}
}
},
"/scenes/{scene}/continue/stream": {
"post": {
"summary": "The same continuation call, streamed as Server-Sent Events",
"x-ability": "generate",
"description": "Emits one `event: delta` per fragment ({\"delta\": \"...\"}), then a terminal `event: done` (ContinueResult payload) or `event: error`. Same persist/ability rules as /continue. Rate-limited to 30 requests/minute per account.",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"instruction": { "type": ["string", "null"] },
"persist": { "type": "boolean", "default": true }
} } } }
},
"responses": {
"200": { "description": "text/event-stream", "content": { "text/event-stream": {} } },
"403": { "$ref": "#/components/responses/Forbidden" },
"429": { "$ref": "#/components/responses/TooManyRequests" }
}
}
},
"/scenes/{scene}/continue/cancel": {
"post": {
"summary": "Stop a continuation stream in flight",
"x-ability": "generate",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/scenes/{scene}/rewrite": {
"post": {
"summary": "Rewrite a passage. Returns the new passage for you to place; persist: true applies it in place (requires write)",
"x-ability": "generate",
"description": "Rate-limited to 30 requests/minute per account.",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["selection"], "properties": {
"selection": { "type": "string", "description": "The passage to rewrite." },
"action": { "type": "string", "description": "A key from GET /rewrite-actions, e.g. 'tighten' or 'user-{id}'. Unknown keys are rejected with 422; put free text in instruction." },
"instruction": { "type": "string", "description": "Free-text alternative to action." },
"persist": { "type": "boolean", "default": false, "description": "Whether to apply the rewritten prose in-place and save the scene (requires write ability)." }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/RewriteResult" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"429": { "$ref": "#/components/responses/TooManyRequests" },
"502": { "$ref": "#/components/responses/UpstreamGenerationError" }
}
}
},
"/scenes/{scene}/rewrite/stream": {
"post": {
"summary": "The same rewrite call, streamed as Server-Sent Events",
"x-ability": "generate",
"description": "Emits one `event: delta` per fragment ({\"delta\": \"...\"}), then a terminal `event: done` or `event: error`. Rate-limited to 30 requests/minute per account.",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "required": ["selection"], "properties": {
"selection": { "type": "string", "description": "The passage to rewrite." },
"action": { "type": "string", "description": "A key from GET /rewrite-actions, e.g. 'tighten' or 'user-{id}'. Unknown keys are rejected with 422; put free text in instruction." },
"instruction": { "type": "string", "description": "Free-text alternative to action." },
"persist": { "type": "boolean", "default": false, "description": "Whether to apply in-place and save (requires write ability)." }
} } } }
},
"responses": {
"200": { "description": "text/event-stream", "content": { "text/event-stream": {} } },
"403": { "$ref": "#/components/responses/Forbidden" },
"429": { "$ref": "#/components/responses/TooManyRequests" }
}
}
},
"/scenes/{scene}/summarize": {
"post": {
"summary": "Summarise a scene. Saves the summary & rebuilds story-so-far by default (needs write too)",
"x-ability": "generate",
"description": "Rate-limited to 30 requests/minute per account.",
"parameters": [{ "name": "scene", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"persist": { "type": "boolean", "default": true }
} } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/SummaryResult" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"429": { "$ref": "#/components/responses/TooManyRequests" },
"502": { "$ref": "#/components/responses/UpstreamGenerationError" }
}
}
},
"/stories/{story}/conversations": {
"get": {
"summary": "List chat conversations for an owned story",
"x-ability": "read",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Conversation" } } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
},
"post": {
"summary": "Create a new chat conversation under an owned story",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": { "application/json": { "schema": { "type": "object", "properties": {
"title": { "type": ["string", "null"] },
"up_to_scene_id": { "type": ["integer", "null"] },
"fidelity": { "type": "string", "enum": ["summary", "verbatim"] },
"model": { "type": ["string", "null"] }
} } } }
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Conversation" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/conversations/{conversation}": {
"get": {
"summary": "Fetch an owned chat conversation and its message history",
"x-ability": "read",
"parameters": [{ "name": "conversation", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Conversation" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
},
"delete": {
"summary": "Delete an owned chat conversation thread",
"x-ability": "write",
"parameters": [{ "name": "conversation", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"204": { "description": "No Content" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/conversations/{conversation}/export": {
"get": {
"summary": "Export an owned chat conversation as a JSON download",
"x-ability": "read",
"parameters": [{ "name": "conversation", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "application/json", "content": { "application/json": {} } },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/stories/{story}/conversations/import": {
"post": {
"summary": "Import a chat conversation into an owned story",
"x-ability": "write",
"parameters": [{ "name": "story", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"content": {
"multipart/form-data": { "schema": { "type": "object", "properties": { "file": { "type": "string", "format": "binary" } } } },
"application/json": { "schema": { "type": "object" } }
}
},
"responses": {
"201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Conversation" } } } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" }
}
}
},
"/conversations/{conversation}/messages": {
"post": {
"summary": "Send a message turn to a chat conversation",
"x-ability": "generate",
"description": "Rate-limited to 30 requests/minute per account.",
"parameters": [{ "name": "conversation", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"required": true,
"content": { "application/json": { "schema": { "type": "object", "required": ["content"], "properties": { "content": { "type": "string" } } } } }
},
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": {
"data": { "type": "object", "properties": {
"message": { "$ref": "#/components/schemas/ChatMessage" },
"model": { "type": "string" },
"usage": { "$ref": "#/components/schemas/GenerationUsage" }
} }
} } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" },
"429": { "$ref": "#/components/responses/TooManyRequests" },
"502": { "$ref": "#/components/responses/UpstreamGenerationError" }
}
}
},
"/conversations/{conversation}/messages/stream": {
"post": {
"summary": "Stream assistant response tokens over Server-Sent Events",
"x-ability": "generate",
"description": "Rate-limited to 30 requests/minute per account.",
"parameters": [{ "name": "conversation", "in": "path", "required": true, "schema": { "type": "integer" } }],
"requestBody": {
"required": true,
"content": { "application/json": { "schema": { "type": "object", "required": ["content"], "properties": { "content": { "type": "string" } } } } }
},
"responses": {
"200": { "description": "text/event-stream", "content": { "text/event-stream": {} } },
"403": { "$ref": "#/components/responses/Forbidden" },
"429": { "$ref": "#/components/responses/TooManyRequests" }
}
}
},
"/conversations/{conversation}/retry": {
"post": {
"summary": "Retry generation of the latest assistant message in a conversation",
"x-ability": "generate",
"description": "Rate-limited to 30 requests/minute per account.",
"parameters": [{ "name": "conversation", "in": "path", "required": true, "schema": { "type": "integer" } }],
"responses": {
"200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": {
"data": { "type": "object", "properties": {
"message": { "$ref": "#/components/schemas/ChatMessage" },
"model": { "type": "string" },
"usage": { "$ref": "#/components/schemas/GenerationUsage" }
} }
} } } } },
"403": { "$ref": "#/components/responses/Forbidden" },
"422": { "$ref": "#/components/responses/ValidationError" },
"429": { "$ref": "#/components/responses/TooManyRequests" },
"502": { "$ref": "#/components/responses/UpstreamGenerationError" }
}
}
}
}
}
If clipboard access is blocked, expand the block and select the text manually. The endpoint details below are the canonical reference the instructions point to.
Authentication
Create a token under API Tokens (in the user menu) and
send it as a bearer header on every request. The base URL is
https://prosie.app.
Authorization: Bearer <your-token>
Accept: application/json
Content-Type: application/json
Each token is scoped to one or more abilities, so a backup
script can be given read
only and can never mutate your manuscript or spend your LLM credits:
List and fetch stories & scenes, and pull Markdown / Word exports.
Create, update and delete stories & scenes.
Run AI continuations, rewrites and summaries (this spends your LLM API credits).
Your first request
List your stories with a read token:
Request
curl https://prosie.app/api/stories \
-H "Authorization: Bearer <your-token>" \
-H "Accept: application/json"
Response 200 OK
{
"data": [
{
"id": 1,
"title": "The Salt Road",
"subtitle": null,
"series_id": null,
"pov": "third",
"tense": "past",
"lore": null,
"characters": null,
"story_so_far": null,
"prose_style": null,
"target_word_count": 90000,
"filter_using_story_so_far": true,
"word_count": 12480,
"target_progress_percent": 14,
"created_at": "2026-06-01T09:12:44+00:00",
"updated_at": "2026-06-14T18:03:10+00:00"
}
]
}
Single-resource endpoints return the same object under a top-level
data key; collections return an array.
Stories
/api/stories
/api/stories/{id}
word_count and (if you set a goal) target_progress_percent.
/api/stories
/api/stories/import-docx
/api/stories/{id}
/api/stories/{id}
204 No Content.
A new story starts with a “Chapter 1” scene by default; send
scaffold: false for a bare story.
You can set series_id (a series you own)
and toggle filter_using_story_so_far —
the same controls the editor exposes.
DOCX imports accept story metadata including subtitle, series, POV, tense, lore, characters, story-so-far,
prose style, target word count and codex filtering. The response includes the story under
data and ordered scene resources under
scenes, including scene IDs and word counts
for verification. Prosie word counts are computed from stored editor HTML after stripping markup, so they can
differ from shell or DOCX tool counts.
Request
curl https://prosie.app/api/stories \
-X POST \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "The Salt Road",
"pov": "third",
"tense": "past",
"target_word_count": 90000
}'
Response 201 Created
{
"data": {
"id": 7,
"title": "The Salt Road",
"subtitle": null,
"series_id": null,
"pov": "third",
"tense": "past",
"lore": null,
"characters": null,
"story_so_far": null,
"prose_style": null,
"target_word_count": 90000,
"filter_using_story_so_far": true,
"word_count": 0,
"target_progress_percent": 0,
"created_at": "2026-06-16T10:01:22+00:00",
"updated_at": "2026-06-16T10:01:22+00:00"
}
}
Series
Manage multi-book series, attach books, and keep a shared codex in sync from external tools. Shared entries are base lore or character records for the whole series; a book can add its own append to one shared entry without replacing the base text.
/api/series
/api/series/{id}
/api/series
/api/series/{id}
/api/series/{id}
/api/series/{series}/stories/{story}
/api/series/{series}/stories/{story}
/api/series/{id}/codex-entries
/api/series/{id}/codex-entries
/api/series-codex-entries/{id}
/api/series-codex-entries/{id}
/api/stories/{story}/series-codex-entries/{entry}/append
/api/stories/{story}/series-codex-entries/{entry}/append
Scenes
/api/stories/{id}/scenes
/api/scenes/{id}
content).
/api/stories/{id}/scenes
order).
/api/scenes/{id}
/api/stories/{id}/scene-summaries
summaries[].scene_id and summaries[].summary.
/api/scenes/{id}
422).
/api/stories/{id}/scenes/reorder
scene_ids (the API version of dragging cards in the Outline).
/api/scenes/{id}/reject-continuation
Bulk summary updates are story-scoped and atomic: every referenced scene must belong to the target story,
or the request returns 422 without changing any summaries.
Example — fetch a scene
curl https://prosie.app/api/scenes/42 \
-H "Authorization: Bearer <your-token>" \
-H "Accept: application/json"
Response 200 OK
{
"data": {
"id": 42,
"story_id": 7,
"order": 0,
"name": "Chapter 1",
"custom_pov": null,
"custom_character": null,
"content": "The wind came off the flats carrying salt and silence.
",
"summary": null,
"word_count": 11,
"created_at": "2026-06-16T10:01:22+00:00",
"updated_at": "2026-06-16T10:04:55+00:00"
}
}
AI generation
These are the voice engine. continue and
summarize save their result by default
(just like the editor), so a default call needs write
on top of generate — pass
persist: false to get the prose or summary back
without touching your manuscript (needs only generate).
rewrite does not save by default — pass
persist: true (needs write) to apply it in place, and undo that with
POST /api/scenes/{id}/rewrite/undo. Every response also
carries the real usage and an
estimated_prompt_tokens figure so you can watch costs.
/api/scenes/{id}/continue
write).
/api/scenes/{id}/continue/stream
/api/scenes/{id}/continue/cancel
/api/scenes/{id}/rewrite
persist: true applies it in place (also needs write). action must be a key from /api/rewrite-actions; free text goes in instruction.
/api/scenes/{id}/rewrite/undo
/api/scenes/{id}/summarize
write).
Example — continue a scene
curl https://prosie.app/api/scenes/42/continue \
-X POST \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{ "instruction": "Bring in the harbourmaster.", "persist": false }'
Response 200 OK
{
"data": {
"prose": "The harbourmaster met them at the jetty, lantern swinging...",
"html": "The harbourmaster met them at the jetty...
",
"persisted": false,
"model": "openai/gpt-5.4-mini",
"usage": {
"prompt_tokens": 1840,
"completion_tokens": 212,
"total_tokens": 2052
},
"estimated_prompt_tokens": 1795
}
}
Example — rewrite a passage
curl https://prosie.app/api/scenes/42/rewrite \
-X POST \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"selection": "He walked very quickly to the door.",
"action": "tighten"
}'
Pass either a free-text instruction or an
action key (a built-in like tighten,
or one of your own prompts — discover keys via GET /api/rewrite-actions).
Response 200 OK
{
"data": {
"prose": "He strode to the door.",
"model": "openai/gpt-5.4-mini",
"usage": { "prompt_tokens": 1320, "completion_tokens": 9, "total_tokens": 1329 },
"estimated_prompt_tokens": 1298
}
}
Example — streamed continuation (SSE)
curl -N https://prosie.app/api/scenes/42/continue/stream \
-X POST \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{ "instruction": "Keep the tension high." }'
Response 200 OK
event: delta
data: {"delta":"The harbourmaster "}
event: delta
data: {"delta":"met them at the jetty"}
event: done
data: {"prose":"The harbourmaster met them at the jetty...","html":"...
","persisted":true,"model":"openai/gpt-5.4-mini","usage":{"prompt_tokens":1840,"completion_tokens":212,"total_tokens":2052},"estimated_prompt_tokens":1795}
You get one delta event per fragment, then a terminal
done (or error) event.
An upstream LLM failure returns 502 on the buffered endpoints.
Codex (your story bible)
The discrete lore & character elements the editor's Codex panel manages.
Each entry has a category
(lore or character),
a name, optional aliases,
and content — these drive Prosie's mention-aware context,
so an external story bible can keep them in sync.
/api/stories/{id}/codex-entries
/api/stories/{id}/codex-entries
/api/codex-entries/{id}
/api/codex-entries/{id}
/api/codex-entries/{id}
Series-shared entries and per-book appends use the Series endpoints above.
Settings & export
/api/llm-config
/api/llm-config
/api/llm-models
/api/rewrite-actions
key the rewrite endpoint accepts.
/api/rewrite-prompts
/api/rewrite-prompts/{id}
/api/rewrite-prompts
/api/rewrite-prompts/{id}
/api/rewrite-prompts/{id}
/api/scenes/{id}/export
?format=docx for Word).
/api/stories/{id}/export
?format=docx).
Example — read your LLM config
curl https://prosie.app/api/llm-config \
-H "Authorization: Bearer <your-token>" \
-H "Accept: application/json"
Response 200 OK
{
"data": {
"provider": "openai",
"model": "openai/gpt-5.4-mini",
"has_openai_key": true,
"has_anthropic_key": false,
"has_openrouter_key": false
}
}