Parse a Foundry/Roll20 VTT export back into KiteFrost content
curl --request POST \
--url https://api.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt \
--header 'Content-Type: application/json' \
--data '
{
"format": "<string>",
"payload": {},
"mode": "skip-existing"
}
'import requests
url = "https://api.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt"
payload = {
"format": "<string>",
"payload": {},
"mode": "skip-existing"
}
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({format: '<string>', payload: {}, mode: 'skip-existing'})
};
fetch('https://api.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt', 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}/ttrpg-gm/import/vtt",
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([
'format' => '<string>',
'payload' => [
],
'mode' => 'skip-existing'
]),
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}/ttrpg-gm/import/vtt"
payload := strings.NewReader("{\n \"format\": \"<string>\",\n \"payload\": {},\n \"mode\": \"skip-existing\"\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.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt")
.header("Content-Type", "application/json")
.body("{\n \"format\": \"<string>\",\n \"payload\": {},\n \"mode\": \"skip-existing\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt")
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 \"format\": \"<string>\",\n \"payload\": {},\n \"mode\": \"skip-existing\"\n}"
response = http.request(request)
puts response.read_body{}{
"error": "Unauthorized",
"code": "unauthorized",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "No valid API key was supplied.",
"doc_url": "https://docs.kitefrost.ai/errors/unauthorized"
}{
"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"
}{
"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"
}ttrpg
Parse a Foundry/Roll20 VTT export back into KiteFrost content
Reverse of the VTT export. Accepts a previously-exported Foundry module or Roll20 JSON and returns a neutral KiteFrost structure (adventure metadata, NPCs, scenes, journal). Parse-only: nothing is persisted - the caller decides what to create. Pro tier and above.
POST
/
v1
/
projects
/
{project_id}
/
ttrpg-gm
/
import
/
vtt
Parse a Foundry/Roll20 VTT export back into KiteFrost content
curl --request POST \
--url https://api.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt \
--header 'Content-Type: application/json' \
--data '
{
"format": "<string>",
"payload": {},
"mode": "skip-existing"
}
'import requests
url = "https://api.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt"
payload = {
"format": "<string>",
"payload": {},
"mode": "skip-existing"
}
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({format: '<string>', payload: {}, mode: 'skip-existing'})
};
fetch('https://api.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt', 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}/ttrpg-gm/import/vtt",
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([
'format' => '<string>',
'payload' => [
],
'mode' => 'skip-existing'
]),
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}/ttrpg-gm/import/vtt"
payload := strings.NewReader("{\n \"format\": \"<string>\",\n \"payload\": {},\n \"mode\": \"skip-existing\"\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.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt")
.header("Content-Type", "application/json")
.body("{\n \"format\": \"<string>\",\n \"payload\": {},\n \"mode\": \"skip-existing\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kitefrost.ai/v1/projects/{project_id}/ttrpg-gm/import/vtt")
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 \"format\": \"<string>\",\n \"payload\": {},\n \"mode\": \"skip-existing\"\n}"
response = http.request(request)
puts response.read_body{}{
"error": "Unauthorized",
"code": "unauthorized",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"message": "No valid API key was supplied.",
"doc_url": "https://docs.kitefrost.ai/errors/unauthorized"
}{
"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"
}{
"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"
}Body
application/json
Request body for the VTT import endpoint.
VTT format: 'foundry' (alias 'foundry_json') or 'roll20'.
The exported VTT JSON object (Foundry module or Roll20 export).
Commit only: skip-existing (default) | merge (update fields) | replace (overwrite). Ignored on parse.
Response
Parsed VTT content (adventure, npcs, scenes, journal, warnings)
The response is of type Response Import Vtt Route V1 Projects Project Id Ttrpg Gm Import Vtt Post · object.
⌘I