curl --request POST \
--url https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes \
--header 'Content-Type: application/json' \
--data '
{
"session_number": 2,
"title": "<string>",
"synopsis": "<string>",
"project_context": "<string>",
"npc_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"encounter_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
]
}
'import requests
url = "https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes"
payload = {
"session_number": 2,
"title": "<string>",
"synopsis": "<string>",
"project_context": "<string>",
"npc_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"encounter_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"]
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
session_number: 2,
title: '<string>',
synopsis: '<string>',
project_context: '<string>',
npc_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
encounter_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a']
})
};
fetch('https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'session_number' => 2,
'title' => '<string>',
'synopsis' => '<string>',
'project_context' => '<string>',
'npc_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'encounter_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes"
payload := strings.NewReader("{\n \"session_number\": 2,\n \"title\": \"<string>\",\n \"synopsis\": \"<string>\",\n \"project_context\": \"<string>\",\n \"npc_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"encounter_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes")
.header("Content-Type", "application/json")
.body("{\n \"session_number\": 2,\n \"title\": \"<string>\",\n \"synopsis\": \"<string>\",\n \"project_context\": \"<string>\",\n \"npc_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"encounter_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"session_number\": 2,\n \"title\": \"<string>\",\n \"synopsis\": \"<string>\",\n \"project_context\": \"<string>\",\n \"npc_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"encounter_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ]\n}"
response = http.request(request)
puts response.read_body{}{
"error": "Not found",
"code": "not_found",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "The requested resource does not exist.",
"doc_url": "https://docs.kitefrost.ai/errors/not_found"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": "Internal server error",
"code": "internal_error",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "An unexpected error occurred.",
"doc_url": "https://docs.kitefrost.ai/errors/internal_error"
}Create scene
Create a Scene (session) with an optional title + synopsis, linking NPCs and encounters for a specific session number. Starts in brainstorm status. Use /sessions/{session_id}/generate to generate narrative content.
Mirrors POST /v1/projects/{project_id}/sessions in the ttrpg-gm pack - same underlying SessionRepo, same response shape. Pack-gated to game-narrative so the dashboard’s pack-aware client picks the right URL without leaking ttrpg-gm-only endpoints to game-narrative tenants.
curl --request POST \
--url https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes \
--header 'Content-Type: application/json' \
--data '
{
"session_number": 2,
"title": "<string>",
"synopsis": "<string>",
"project_context": "<string>",
"npc_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"encounter_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
]
}
'import requests
url = "https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes"
payload = {
"session_number": 2,
"title": "<string>",
"synopsis": "<string>",
"project_context": "<string>",
"npc_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"encounter_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"]
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
session_number: 2,
title: '<string>',
synopsis: '<string>',
project_context: '<string>',
npc_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
encounter_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a']
})
};
fetch('https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'session_number' => 2,
'title' => '<string>',
'synopsis' => '<string>',
'project_context' => '<string>',
'npc_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'encounter_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes"
payload := strings.NewReader("{\n \"session_number\": 2,\n \"title\": \"<string>\",\n \"synopsis\": \"<string>\",\n \"project_context\": \"<string>\",\n \"npc_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"encounter_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes")
.header("Content-Type", "application/json")
.body("{\n \"session_number\": 2,\n \"title\": \"<string>\",\n \"synopsis\": \"<string>\",\n \"project_context\": \"<string>\",\n \"npc_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"encounter_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-staging.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"session_number\": 2,\n \"title\": \"<string>\",\n \"synopsis\": \"<string>\",\n \"project_context\": \"<string>\",\n \"npc_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"encounter_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ]\n}"
response = http.request(request)
puts response.read_body{}{
"error": "Not found",
"code": "not_found",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "The requested resource does not exist.",
"doc_url": "https://docs.kitefrost.ai/errors/not_found"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": "Internal server error",
"code": "internal_error",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "An unexpected error occurred.",
"doc_url": "https://docs.kitefrost.ai/errors/internal_error"
}Path Parameters
Body
POST /v1/projects/{project_id}/sessions
title is required (2026-05-16, migration 094). session_number
alone is not a unique identifier - there is no UNIQUE constraint on
(project_id, session_number) and series_id is auto-generated per
session today, so two Game Nights both numbered "1" are valid state.
Without a title the dashboard list shows two indistinguishable rows.
x >= 1User-facing session name
1 - 200Short prose summary of the session
500050000NPCs to include
Encounters to include
Response
Scene created
The response is of type Response Create Scene V1 Projects Project Id Game Narrative Scenes Post · object.