Skip to main content

LLM / AI

AI-powered endpoints for generating content, running quality checks, and preprocessing prompts. All routes pass through a general rate limiter (120 req/min by default). Generation and analysis endpoints have additional stricter limiters.

Endpoints overview​

MethodEndpointPermissionDescription
POST/api/llm/ideasllm:generateGenerate story ideas
POST/api/llm/alternativesllm:generateGenerate alternatives
POST/api/llm/prompt-preprocessauthenticatedPreprocess prompt
POST/api/llm/generate-scenesllm:generateGenerate episode scenes
GET/api/llm/canon-check/:episodeIdauthenticatedGet canon check result
POST/api/llm/canon-checkllm:analyzeRun canon check
GET/api/llm/continuity-check/:episodeIdauthenticatedGet continuity check result
POST/api/llm/continuity-checkllm:analyzeRun continuity check
GET/api/llm/safety-check/:episodeIdauthenticatedGet safety check result
POST/api/llm/safety-checkllm:analyzeRun safety check
POST/api/llm/final-approvellm:approveFinal approve episode

Content Generation​

Generate story ideas​

POST /api/llm/ideas — Auth required, Rate limited, Permission: llm:generate

Request body

{
"projectId": "proj-uuid-001",
"userPrompt": "The heroes discover a hidden door behind the waterfall",
"scene": {
"id": 3,
"title": "The Cave",
"mood": "mysterious",
"blocks": [
{ "type": "action", "text": "They enter the cave cautiously" }
]
},
"includeContext": true
}
FieldRequiredNotes
projectIdyesUUID of the project (used to load characters/world rules for context)
userPromptyesThe writer's idea prompt
sceneyesCurrent scene object with blocks
includeContextnoInclude broader episode context; defaults to false

Response — 200

{
"ideas": [
"Mia notices ancient symbols carved into the stone",
"A soft glow emanates from behind a moss-covered wall",
"The sound of rushing water grows louder as they move deeper"
]
}

Generate alternatives​

POST /api/llm/alternatives — Auth required, Rate limited, Permission: llm:generate

Suggests alternative phrasings or directions for existing blocks in a scene.

Request body

{
"projectId": "proj-uuid-001",
"scene": {
"id": 3,
"title": "The Cave",
"blocks": [
{ "type": "dialog", "text": "We need to leave. Now.", "characterId": "char-uuid-001" }
]
},
"userPrompt": "Make it more urgent and emotional"
}
FieldRequiredNotes
projectIdyesUUID of the project
sceneyesCurrent scene with blocks
userPromptnoOptional direction hint

Response — 200

{
"alternatives": [
"Run! We can't stay here another second!",
"There's no time — go, go, go!",
"Something's wrong. We have to get out right now."
]
}

Preprocess prompt​

POST /api/llm/prompt-preprocess — Auth required, Rate limited

Cleans and enriches a raw user-written prompt before using it for image or video generation.

Request body

{
"prompt": "girl in forest with big tree",
"projectId": "proj-uuid-001",
"target": "image",
"context": "generic"
}
FieldRequiredNotes
promptyesRaw prompt text
projectIdnoIf provided, project-specific style settings are applied
targetnoimage | video; defaults to "image"
contextnogeneric | character | location; defaults to "generic"

Response — 200

{
"processedPrompt": "A young girl standing in a lush forest, beside an ancient towering oak tree, warm afternoon light filtering through the leaves, cinematic, photorealistic"
}

Generate episode scenes​

POST /api/llm/generate-scenes — Auth required, Rate limited, Permission: llm:generate

Generates a complete set of scenes (with blocks) for an episode and persists them to the database.

Request body

{
"projectId": "proj-uuid-001",
"episodeId": "ep-uuid-001",
"prompt": "The heroes discover the villain's hidden base and must escape before the alarm goes off",
"sceneCount": 3
}
FieldRequiredNotes
projectIdyesUUID of the project
episodeIdyesUUID of the episode to populate
promptyesEpisode synopsis (min 5 chars)
sceneCountnoNumber of scenes to generate; defaults to 3

Response — 200

{ "success": true }

Quality Checks​

All check endpoints come in a GET (retrieve cached result) and POST (trigger a new analysis) pair. Results are persisted in the database.

Canon check​

GET /api/llm/canon-check/:episodeId — Auth required

Returns the most recently saved canon check result for the episode.

Response — 200

{
"episodeId": "ep-uuid-001",
"checkedAt": "2025-04-10T12:00:00.000Z",
"passed": false,
"issues": [
{
"sceneId": 3,
"blockId": "block-uuid-007",
"description": "Mia uses a smartphone, but world rules state no electronics exist in this era.",
"severity": "high"
}
]
}

POST /api/llm/canon-check — Auth required, Rate limited, Permission: llm:analyze

Runs a new canon check against all project world rules and saves the result.

Request body

{ "episodeId": "ep-uuid-001" }

Response — 200 — same shape as GET result above


Continuity check​

GET /api/llm/continuity-check/:episodeId — Auth required

Returns the most recently saved continuity check result.

Response — 200

{
"episodeId": "ep-uuid-001",
"checkedAt": "2025-04-10T12:05:00.000Z",
"passed": true,
"issues": []
}

POST /api/llm/continuity-check — Auth required, Rate limited, Permission: llm:analyze

Checks for internal contradictions within the episode (character state, timeline, locations).

Request body

{ "episodeId": "ep-uuid-001" }

Response — 200 — same shape as GET result above


Safety check​

GET /api/llm/safety-check/:episodeId — Auth required

Returns the most recently saved safety check result.

Response — 200

{
"episodeId": "ep-uuid-001",
"checkedAt": "2025-04-10T12:10:00.000Z",
"passed": true,
"score": 98,
"issues": []
}

POST /api/llm/safety-check — Auth required, Rate limited, Permission: llm:analyze

Checks episode content for age-appropriateness and policy violations based on the project's target audience settings.

Request body

{ "episodeId": "ep-uuid-001" }

Response — 200 — same shape as GET result above


Approval​

Final approve​

POST /api/llm/final-approve — Auth required, Rate limited, Permission: llm:approve

Runs canon, continuity, and safety checks in sequence. If all pass, marks the episode as approved and creates a completion task for the authenticated user.

Request body

{ "episodeId": "ep-uuid-001" }

Response — 200

{
"approved": true,
"episodeId": "ep-uuid-001",
"canonCheck": { "passed": true },
"continuityCheck": { "passed": true },
"safetyCheck": { "passed": true, "score": 98 }
}