Session
POST /session-handshake
Create a handshake token for secure authentication
Required scope: session:manage
Parameters
| Name | Type | Required | Source | Description |
|---|---|---|---|---|
| x-api-key | string | ✓ | header | API key header |
| x-foundry-url | string | ✓ | header | Foundry URL header |
| x-username | string | ✓ | header | Username header |
| x-world-name | string | header | World name header |
Returns
object - Handshake token and encryption details
Try It Out
POST /start-session
Start a headless Foundry session using puppeteer
Required scope: session:manage
Parameters
| Name | Type | Required | Source | Description |
|---|---|---|---|---|
| handshakeToken | string | ✓ | body | The token received from session-handshake |
| encryptedPassword | string | ✓ | body | Password encrypted with the public key |
| x-api-key | string | ✓ | header | API key header |
| captureBrowserConsole | string | body | Log level for browser console capture ("error", "warn", or "debug") |
Returns
object - Session information including sessionId and clientId
Try It Out
Code Examples
- JavaScript
- cURL
- Python
- TypeScript
- Emojicode
// Step 1: Get handshake credentials
const baseUrl = 'http://localhost:3010';
const apiKey = 'your-api-key';
const handshakeResponse = await fetch(`${baseUrl}/session-handshake`, {
method: 'POST',
headers: {
'x-api-key': apiKey,
'x-foundry-url': 'http://localhost:30000',
'x-world-name': 'my-world',
'x-username': 'Gamemaster'
}
});
const { token, publicKey, nonce } = await handshakeResponse.json();
// Step 2: Encrypt password using Web Crypto API (RSA-OAEP with SHA-256)
const password = 'your-password';
const payload = JSON.stringify({ password, nonce });
// Import the public key
const pemContents = publicKey
.replace('-----BEGIN PUBLIC KEY-----', '')
.replace('-----END PUBLIC KEY-----', '')
.replace(/\n/g, '');
const binaryKey = Uint8Array.from(atob(pemContents), c => c.charCodeAt(0));
const cryptoKey = await crypto.subtle.importKey(
'spki',
binaryKey,
{ name: 'RSA-OAEP', hash: 'SHA-256' },
false,
['encrypt']
);
// Encrypt the payload
const encrypted = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
cryptoKey,
new TextEncoder().encode(payload)
);
const encryptedPassword = btoa(String.fromCharCode(...new Uint8Array(encrypted)));
// Step 3: Start the session
const sessionResponse = await fetch(`${baseUrl}/start-session`, {
method: 'POST',
headers: {
'x-api-key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ handshakeToken: token, encryptedPassword })
});
const { sessionId, clientId } = await sessionResponse.json();
console.log('Session started:', { sessionId, clientId });
# Session creation requires encryption - use the JavaScript, Python, or TypeScript examples.
#
# The flow is:
# 1. POST /session-handshake to get token, publicKey, nonce
# 2. Encrypt JSON payload { password, nonce } with RSA-OAEP using publicKey
# 3. POST /start-session with { handshakeToken, encryptedPassword }
#
# Example handshake request:
curl -X POST 'http://localhost:3010/session-handshake' \
-H "x-api-key: your-api-key" \
-H "x-foundry-url: http://localhost:30000" \
-H "x-world-name: my-world" \
-H "x-username: Gamemaster"
import requests
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
import base64
import json
base_url = 'http://localhost:3010'
api_key = 'your-api-key'
# Step 1: Get handshake credentials
handshake_response = requests.post(
f'{base_url}/session-handshake',
headers={
'x-api-key': api_key,
'x-foundry-url': 'http://localhost:30000',
'x-world-name': 'my-world',
'x-username': 'Gamemaster'
}
)
handshake_data = handshake_response.json()
token = handshake_data['token']
public_key_pem = handshake_data['publicKey']
nonce = handshake_data['nonce']
# Step 2: Encrypt password using RSA-OAEP with SHA-256
password = 'your-password'
payload = json.dumps({'password': password, 'nonce': nonce})
public_key = serialization.load_pem_public_key(public_key_pem.encode())
encrypted = public_key.encrypt(
payload.encode(),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
encrypted_password = base64.b64encode(encrypted).decode()
# Step 3: Start the session
session_response = requests.post(
f'{base_url}/start-session',
headers={'x-api-key': api_key},
json={'handshakeToken': token, 'encryptedPassword': encrypted_password}
)
session_data = session_response.json()
print('Session started:', session_data)
import crypto from 'crypto';
// Step 1: Get handshake credentials
const baseUrl = 'http://localhost:3010';
const apiKey = 'your-api-key';
const handshakeResponse = await fetch(`${baseUrl}/session-handshake`, {
method: 'POST',
headers: {
'x-api-key': apiKey,
'x-foundry-url': 'http://localhost:30000',
'x-world-name': 'my-world',
'x-username': 'Gamemaster'
}
});
const { token, publicKey, nonce } = await handshakeResponse.json();
// Step 2: Encrypt password using Node.js crypto (RSA-OAEP with SHA-256)
const password = 'your-password';
const payload = JSON.stringify({ password, nonce });
const encryptedPassword = crypto.publicEncrypt(
{ key: publicKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' },
Buffer.from(payload, 'utf8')
).toString('base64');
// Step 3: Start the session
const sessionResponse = await fetch(`${baseUrl}/start-session`, {
method: 'POST',
headers: {
'x-api-key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({ handshakeToken: token, encryptedPassword })
});
const { sessionId, clientId } = await sessionResponse.json();
console.log('Session started:', { sessionId, clientId });
Just don't 😂
Response
Status: 200
{ "clientId": "fvtt_099ad17ea199e7e3", "sessionId": "030a1249-70e5-41a8-8bd3-26cfeb39faab", "success": true}
DELETE /end-session
Stop a headless Foundry session
Required scope: session:manage
Parameters
| Name | Type | Required | Source | Description |
|---|---|---|---|---|
| sessionId | string | ✓ | query | The ID of the session to end |
| x-api-key | string | ✓ | header | API key header |
Returns
object - Status of the operation
Try It Out
Code Examples
- JavaScript
- cURL
- Python
- TypeScript
- Emojicode
const baseUrl = 'http://localhost:3010';
const path = '/end-session';
const params = {
sessionId: '030a1249-70e5-41a8-8bd3-26cfeb39faab'
};
const queryString = new URLSearchParams(params).toString();
const url = `${baseUrl}${path}?${queryString}`;
const response = await fetch(url, {
method: 'DELETE',
headers: {
'x-api-key': 'your-api-key-here'
}
});
const data = await response.json();
console.log(data);
curl -X DELETE 'http://localhost:3010/end-session?sessionId=030a1249-70e5-41a8-8bd3-26cfeb39faab' \
-H "x-api-key: your-api-key-here"
import requests
base_url = 'http://localhost:3010'
path = '/end-session'
params = {
'sessionId': '030a1249-70e5-41a8-8bd3-26cfeb39faab'
}
url = f'{base_url}{path}'
response = requests.delete(
url,
params=params,
headers={
'x-api-key': 'your-api-key-here'
}
)
data = response.json()
print(data)
import axios from 'axios';
(async () => {
const baseUrl = 'http://localhost:3010';
const path = '/end-session';
const params = {
sessionId: '030a1249-70e5-41a8-8bd3-26cfeb39faab'
};
const queryString = new URLSearchParams(params).toString();
const url = `${baseUrl}${path}?${queryString}`;
const response = await axios({
method: 'delete',
headers: {
'x-api-key': 'your-api-key-here'
},
url
});
const data = response.data;
console.log(data);
})();
📦 sockets 🏠
💭 Emojicode HTTP Client
💭 Compile: emojicodec example.🍇 -o example
💭 Run: ./example
🏁 🍇
💭 Connection settings
🔤localhost🔤 ➡️ host
3010 ➡️ port
🔤/end-session🔤 ➡️ path
💭 Query parameters
🔤sessionId=030a1249-70e5-41a8-8bd3-26cfeb39faab🔤 ➡️ sessionId
🔤?🧲sessionId🧲🔤 ➡️ queryString
💭 Build HTTP request
🔤DELETE /end-session🧲queryString🧲 HTTP/1.1❌r❌nHost: localhost:3010❌r❌nx-api-key: your-api-key-here❌r❌n❌r❌n🔤 ➡️ request
💭 Connect and send
🍺 🆕📞 host port❗ ➡️ socket
🍺 💬 socket 📇 request❗❗
💭 Read and print response
🍺 👂 socket 4096❗ ➡️ data
😀 🍺 🔡 data❗❗
💭 Close socket
🚪 socket❗
🍉
Response
Status: 200
{ "message": "Session ended", "status": "ok"}
GET /session
Get all active headless Foundry sessions
Required scope: session:manage
Parameters
| Name | Type | Required | Source | Description |
|---|---|---|---|---|
| x-api-key | string | ✓ | header | API key header |
Returns
object - List of active sessions for the current API key
Try It Out
Code Examples
- JavaScript
- cURL
- Python
- TypeScript
- Emojicode
const baseUrl = 'http://localhost:3010';
const path = '/session';
const url = `${baseUrl}${path}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'x-api-key': 'your-api-key-here'
}
});
const data = await response.json();
console.log(data);
curl -X GET 'http://localhost:3010/session' \
-H "x-api-key: your-api-key-here"
import requests
base_url = 'http://localhost:3010'
path = '/session'
url = f'{base_url}{path}'
response = requests.get(
url,
headers={
'x-api-key': 'your-api-key-here'
}
)
data = response.json()
print(data)
import axios from 'axios';
(async () => {
const baseUrl = 'http://localhost:3010';
const path = '/session';
const url = `${baseUrl}${path}`;
const response = await axios({
method: 'get',
headers: {
'x-api-key': 'your-api-key-here'
},
url
});
const data = response.data;
console.log(data);
})();
📦 sockets 🏠
💭 Emojicode HTTP Client
💭 Compile: emojicodec example.🍇 -o example
💭 Run: ./example
🏁 🍇
💭 Connection settings
🔤localhost🔤 ➡️ host
3010 ➡️ port
🔤/session🔤 ➡️ path
💭 Build HTTP request
🔤GET /session HTTP/1.1❌r❌nHost: localhost:3010❌r❌nx-api-key: your-api-key-here❌r❌n❌r❌n🔤 ➡️ request
💭 Connect and send
🍺 🆕📞 host port❗ ➡️ socket
🍺 💬 socket 📇 request❗❗
💭 Read and print response
🍺 👂 socket 4096❗ ➡️ data
😀 🍺 🔡 data❗❗
💭 Close socket
🚪 socket❗
🍉
Response
Status: 200
{ "activeSessions": [ 0: { "clientId": "fvtt_099ad17ea199e7e3", "foundryUrl": "http://localhost:30013", "foundryVersion": "13.348", "lastActivity": 1778896411477, "sessionId": "030a1249-70e5-41a8-8bd3-26cfeb39faab", "startedAt": 1778896411477, "systemId": "dnd5e", "systemTitle": "Dungeons & Dragons Fifth Edition", "systemVersion": "5.0.4", "username": "tester", "worldId": "testing", "worldName": "testing", "worldTitle": "testing" } ]}