curl --request POST \
--url https://api-staging.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/encounters \
--header 'Content-Type: application/json' \
--data '
{
"description": "Ambush by bandits on the mountain pass",
"difficulty": "hard",
"environment": "mountain_pass",
"game_system": "dnd5e",
"party_level": 5,
"party_size": 4,
"project_context": "A treacherous mountain range in a dark fantasy project"
}
'import requests
url = "https://api-staging.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/encounters"
payload = {
"description": "Ambush by bandits on the mountain pass",
"difficulty": "hard",
"environment": "mountain_pass",
"game_system": "dnd5e",
"party_level": 5,
"party_size": 4,
"project_context": "A treacherous mountain range in a dark fantasy project"
}
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({
description: 'Ambush by bandits on the mountain pass',
difficulty: 'hard',
environment: 'mountain_pass',
game_system: 'dnd5e',
party_level: 5,
party_size: 4,
project_context: 'A treacherous mountain range in a dark fantasy project'
})
};
fetch('https://api-staging.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/encounters', 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}/ttrpg-gm/encounters",
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([
'description' => 'Ambush by bandits on the mountain pass',
'difficulty' => 'hard',
'environment' => 'mountain_pass',
'game_system' => 'dnd5e',
'party_level' => 5,
'party_size' => 4,
'project_context' => 'A treacherous mountain range in a dark fantasy project'
]),
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}/ttrpg-gm/encounters"
payload := strings.NewReader("{\n \"description\": \"Ambush by bandits on the mountain pass\",\n \"difficulty\": \"hard\",\n \"environment\": \"mountain_pass\",\n \"game_system\": \"dnd5e\",\n \"party_level\": 5,\n \"party_size\": 4,\n \"project_context\": \"A treacherous mountain range in a dark fantasy project\"\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}/ttrpg-gm/encounters")
.header("Content-Type", "application/json")
.body("{\n \"description\": \"Ambush by bandits on the mountain pass\",\n \"difficulty\": \"hard\",\n \"environment\": \"mountain_pass\",\n \"game_system\": \"dnd5e\",\n \"party_level\": 5,\n \"party_size\": 4,\n \"project_context\": \"A treacherous mountain range in a dark fantasy project\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-staging.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/encounters")
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 \"description\": \"Ambush by bandits on the mountain pass\",\n \"difficulty\": \"hard\",\n \"environment\": \"mountain_pass\",\n \"game_system\": \"dnd5e\",\n \"party_level\": 5,\n \"party_size\": 4,\n \"project_context\": \"A treacherous mountain range in a dark fantasy project\"\n}"
response = http.request(request)
puts response.read_body{
"created_at": "2026-01-01T12:00:00Z",
"description": "Ambush by bandits on the mountain pass",
"difficulty": "hard",
"encounter_id": "enc_abc123",
"project_id": "proj_abc123",
"session_id": "sess_xyz789",
"status": "brainstorm"
}{
"error": "Forbidden",
"code": "forbidden",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "Your API key does not have the required scope.",
"doc_url": "https://docs.kitefrost.ai/errors/forbidden"
}{
"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 encounter
Create a TTRPG encounter record with difficulty, party info, and optional world context. The encounter starts in brainstorm status. Use /encounters/{encounter_id}/generate to generate content.
curl --request POST \
--url https://api-staging.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/encounters \
--header 'Content-Type: application/json' \
--data '
{
"description": "Ambush by bandits on the mountain pass",
"difficulty": "hard",
"environment": "mountain_pass",
"game_system": "dnd5e",
"party_level": 5,
"party_size": 4,
"project_context": "A treacherous mountain range in a dark fantasy project"
}
'import requests
url = "https://api-staging.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/encounters"
payload = {
"description": "Ambush by bandits on the mountain pass",
"difficulty": "hard",
"environment": "mountain_pass",
"game_system": "dnd5e",
"party_level": 5,
"party_size": 4,
"project_context": "A treacherous mountain range in a dark fantasy project"
}
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({
description: 'Ambush by bandits on the mountain pass',
difficulty: 'hard',
environment: 'mountain_pass',
game_system: 'dnd5e',
party_level: 5,
party_size: 4,
project_context: 'A treacherous mountain range in a dark fantasy project'
})
};
fetch('https://api-staging.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/encounters', 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}/ttrpg-gm/encounters",
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([
'description' => 'Ambush by bandits on the mountain pass',
'difficulty' => 'hard',
'environment' => 'mountain_pass',
'game_system' => 'dnd5e',
'party_level' => 5,
'party_size' => 4,
'project_context' => 'A treacherous mountain range in a dark fantasy project'
]),
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}/ttrpg-gm/encounters"
payload := strings.NewReader("{\n \"description\": \"Ambush by bandits on the mountain pass\",\n \"difficulty\": \"hard\",\n \"environment\": \"mountain_pass\",\n \"game_system\": \"dnd5e\",\n \"party_level\": 5,\n \"party_size\": 4,\n \"project_context\": \"A treacherous mountain range in a dark fantasy project\"\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}/ttrpg-gm/encounters")
.header("Content-Type", "application/json")
.body("{\n \"description\": \"Ambush by bandits on the mountain pass\",\n \"difficulty\": \"hard\",\n \"environment\": \"mountain_pass\",\n \"game_system\": \"dnd5e\",\n \"party_level\": 5,\n \"party_size\": 4,\n \"project_context\": \"A treacherous mountain range in a dark fantasy project\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-staging.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/encounters")
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 \"description\": \"Ambush by bandits on the mountain pass\",\n \"difficulty\": \"hard\",\n \"environment\": \"mountain_pass\",\n \"game_system\": \"dnd5e\",\n \"party_level\": 5,\n \"party_size\": 4,\n \"project_context\": \"A treacherous mountain range in a dark fantasy project\"\n}"
response = http.request(request)
puts response.read_body{
"created_at": "2026-01-01T12:00:00Z",
"description": "Ambush by bandits on the mountain pass",
"difficulty": "hard",
"encounter_id": "enc_abc123",
"project_id": "proj_abc123",
"session_id": "sess_xyz789",
"status": "brainstorm"
}{
"error": "Forbidden",
"code": "forbidden",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "Your API key does not have the required scope.",
"doc_url": "https://docs.kitefrost.ai/errors/forbidden"
}{
"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"
}Body
POST /v1/projects/{project_id}/encounters
Brief description of the encounter scenario
1 - 500Encounter difficulty: easy | medium | hard | deadly
^(easy|medium|hard|deadly)$Average party level (1-20)
1 <= x <= 20Number of players in the party
1 <= x <= 10Game system (e.g. dnd5e, pathfinder2e)
50Project or setting description
50000Physical environment (e.g. dungeon, forest, city)
100When true, generate a top-down battle-map / token image via the configured image provider. Returns the URL on the EncounterResponse. Silently degrades to no image if no provider is configured or the prompt is blocked by content safety.
Response
Encounter created
Encounter resource representation.
Unique encounter identifier
Brief description of the encounter scenario
Project this encounter belongs to
Human-readable slug for the URL (/projects/<slug>/encounters/<external_id>). Derived from description at create time and immutable. May be null on rows that pre-date migration 102 backfill - frontend falls back to id. See docs/conventions/dashboard-slug-urls.md.
Lifecycle status: brainstorm | draft | final
Encounter difficulty level
Active generation session ID, if any
ISO 8601 creation timestamp
URL of a generated battle-map / token image when generate_token=true was passed and image generation succeeded. Null otherwise.