curl --request PATCH \
--url https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id} \
--header 'Content-Type: application/json' \
--data '
{
"title": "<string>",
"synopsis": "<string>",
"project_context": "<string>",
"npc_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"encounter_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"data": {}
}
'import requests
url = "https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}"
payload = {
"title": "<string>",
"synopsis": "<string>",
"project_context": "<string>",
"npc_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"encounter_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"data": {}
}
headers = {"Content-Type": "application/json"}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
title: '<string>',
synopsis: '<string>',
project_context: '<string>',
npc_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
encounter_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
data: {}
})
};
fetch('https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}', 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.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'title' => '<string>',
'synopsis' => '<string>',
'project_context' => '<string>',
'npc_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'encounter_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'data' => [
]
]),
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.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}"
payload := strings.NewReader("{\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 \"data\": {}\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}")
.header("Content-Type", "application/json")
.body("{\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 \"data\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\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 \"data\": {}\n}"
response = http.request(request)
puts response.read_body{}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": "Rate limit exceeded",
"code": "rate_limited",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "You have exceeded the rate limit for your plan.",
"doc_url": "https://docs.kitefrost.ai/errors/rate_limited"
}{
"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"
}Update scene
Partial-update a Scene’s mutable fields (title, synopsis, project_context, npc_ids, encounter_ids, data). Each field is independently optional; None means ‘leave unchanged’ (not ‘set to NULL’). Returns 404 if the scene does not exist or belongs to a different tenant/project.
curl --request PATCH \
--url https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id} \
--header 'Content-Type: application/json' \
--data '
{
"title": "<string>",
"synopsis": "<string>",
"project_context": "<string>",
"npc_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"encounter_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"data": {}
}
'import requests
url = "https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}"
payload = {
"title": "<string>",
"synopsis": "<string>",
"project_context": "<string>",
"npc_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"encounter_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"data": {}
}
headers = {"Content-Type": "application/json"}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
title: '<string>',
synopsis: '<string>',
project_context: '<string>',
npc_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
encounter_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
data: {}
})
};
fetch('https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}', 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.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'title' => '<string>',
'synopsis' => '<string>',
'project_context' => '<string>',
'npc_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'encounter_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'data' => [
]
]),
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.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}"
payload := strings.NewReader("{\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 \"data\": {}\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}")
.header("Content-Type", "application/json")
.body("{\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 \"data\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kitefrost.ai/v1/projects/{project_id}/game-narrative/scenes/{session_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\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 \"data\": {}\n}"
response = http.request(request)
puts response.read_body{}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"error": "Rate limit exceeded",
"code": "rate_limited",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "You have exceeded the rate limit for your plan.",
"doc_url": "https://docs.kitefrost.ai/errors/rate_limited"
}{
"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
Body for PATCH .../sessions/{id} - partial update of mutable scene fields.
Mirrors the full SessionRepo.update _ALLOWED set: title, synopsis, project_context, npc_ids, encounter_ids, data. Each is independently optional; None means "leave unchanged" (not "set to NULL").
title/synopsis are the same human-editable fields exposed by
CreateSessionRequest and the ttrpg-gm UpdateSessionRequest - they MUST
be accepted here so the dashboard edit form (which submits both) does
not fail validation with "Extra inputs are not permitted". title
cannot be cleared (NOT NULL since migration 094); when present it must
be non-empty. Limits mirror web/utils/field-limits.ts sessions.*.
Response
Updated scene
The response is of type Response Patch Scene V1 Projects Project Id Game Narrative Scenes Session Id Patch · object.