curl --request POST \
--url https://app.harmonica.chat/api/v1/sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"topic": "<string>",
"goal": "<string>",
"context": "<string>",
"critical": "<string>",
"prompt": "<string>",
"template_id": "<string>",
"project_id": "<string>",
"roster": [
{
"email": "jsmith@example.com",
"displayName": "<string>",
"auth0Sub": "<string>",
"rolesByStep": {}
}
],
"questions": [
{
"text": "<string>"
}
],
"cross_pollination": false,
"widgets_enabled": false,
"results_visibility": "host",
"distribution": [
{
"group_id": "<string>"
}
]
}
'import requests
url = "https://app.harmonica.chat/api/v1/sessions"
payload = {
"topic": "<string>",
"goal": "<string>",
"context": "<string>",
"critical": "<string>",
"prompt": "<string>",
"template_id": "<string>",
"project_id": "<string>",
"roster": [
{
"email": "jsmith@example.com",
"displayName": "<string>",
"auth0Sub": "<string>",
"rolesByStep": {}
}
],
"questions": [{ "text": "<string>" }],
"cross_pollination": False,
"widgets_enabled": False,
"results_visibility": "host",
"distribution": [{ "group_id": "<string>" }]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
topic: '<string>',
goal: '<string>',
context: '<string>',
critical: '<string>',
prompt: '<string>',
template_id: '<string>',
project_id: '<string>',
roster: [
{
email: 'jsmith@example.com',
displayName: '<string>',
auth0Sub: '<string>',
rolesByStep: {}
}
],
questions: [{text: '<string>'}],
cross_pollination: false,
widgets_enabled: false,
results_visibility: 'host',
distribution: [{group_id: '<string>'}]
})
};
fetch('https://app.harmonica.chat/api/v1/sessions', 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://app.harmonica.chat/api/v1/sessions",
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([
'topic' => '<string>',
'goal' => '<string>',
'context' => '<string>',
'critical' => '<string>',
'prompt' => '<string>',
'template_id' => '<string>',
'project_id' => '<string>',
'roster' => [
[
'email' => 'jsmith@example.com',
'displayName' => '<string>',
'auth0Sub' => '<string>',
'rolesByStep' => [
]
]
],
'questions' => [
[
'text' => '<string>'
]
],
'cross_pollination' => false,
'widgets_enabled' => false,
'results_visibility' => 'host',
'distribution' => [
[
'group_id' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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://app.harmonica.chat/api/v1/sessions"
payload := strings.NewReader("{\n \"topic\": \"<string>\",\n \"goal\": \"<string>\",\n \"context\": \"<string>\",\n \"critical\": \"<string>\",\n \"prompt\": \"<string>\",\n \"template_id\": \"<string>\",\n \"project_id\": \"<string>\",\n \"roster\": [\n {\n \"email\": \"jsmith@example.com\",\n \"displayName\": \"<string>\",\n \"auth0Sub\": \"<string>\",\n \"rolesByStep\": {}\n }\n ],\n \"questions\": [\n {\n \"text\": \"<string>\"\n }\n ],\n \"cross_pollination\": false,\n \"widgets_enabled\": false,\n \"results_visibility\": \"host\",\n \"distribution\": [\n {\n \"group_id\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://app.harmonica.chat/api/v1/sessions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"topic\": \"<string>\",\n \"goal\": \"<string>\",\n \"context\": \"<string>\",\n \"critical\": \"<string>\",\n \"prompt\": \"<string>\",\n \"template_id\": \"<string>\",\n \"project_id\": \"<string>\",\n \"roster\": [\n {\n \"email\": \"jsmith@example.com\",\n \"displayName\": \"<string>\",\n \"auth0Sub\": \"<string>\",\n \"rolesByStep\": {}\n }\n ],\n \"questions\": [\n {\n \"text\": \"<string>\"\n }\n ],\n \"cross_pollination\": false,\n \"widgets_enabled\": false,\n \"results_visibility\": \"host\",\n \"distribution\": [\n {\n \"group_id\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.harmonica.chat/api/v1/sessions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"topic\": \"<string>\",\n \"goal\": \"<string>\",\n \"context\": \"<string>\",\n \"critical\": \"<string>\",\n \"prompt\": \"<string>\",\n \"template_id\": \"<string>\",\n \"project_id\": \"<string>\",\n \"roster\": [\n {\n \"email\": \"jsmith@example.com\",\n \"displayName\": \"<string>\",\n \"auth0Sub\": \"<string>\",\n \"rolesByStep\": {}\n }\n ],\n \"questions\": [\n {\n \"text\": \"<string>\"\n }\n ],\n \"cross_pollination\": false,\n \"widgets_enabled\": false,\n \"results_visibility\": \"host\",\n \"distribution\": [\n {\n \"group_id\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"topic": "<string>",
"goal": "<string>",
"status": "active",
"participant_count": 123,
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"join_url": "<string>",
"chain": {
"status": "started",
"chainInstanceId": "<string>",
"stepId": "<string>",
"message": "<string>",
"reason": "<string>"
}
}{
"error": {
"code": "validation_error",
"message": "topic is required"
}
}{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key"
}
}Create session
Creates a new deliberation session owned by the authenticated user.
If prompt is omitted, a facilitation prompt is auto-generated server-side
from the topic, goal, context, and critical fields (with a fallback to a basic prompt).
Returns the created session with a join_url for sharing with participants.
curl --request POST \
--url https://app.harmonica.chat/api/v1/sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"topic": "<string>",
"goal": "<string>",
"context": "<string>",
"critical": "<string>",
"prompt": "<string>",
"template_id": "<string>",
"project_id": "<string>",
"roster": [
{
"email": "jsmith@example.com",
"displayName": "<string>",
"auth0Sub": "<string>",
"rolesByStep": {}
}
],
"questions": [
{
"text": "<string>"
}
],
"cross_pollination": false,
"widgets_enabled": false,
"results_visibility": "host",
"distribution": [
{
"group_id": "<string>"
}
]
}
'import requests
url = "https://app.harmonica.chat/api/v1/sessions"
payload = {
"topic": "<string>",
"goal": "<string>",
"context": "<string>",
"critical": "<string>",
"prompt": "<string>",
"template_id": "<string>",
"project_id": "<string>",
"roster": [
{
"email": "jsmith@example.com",
"displayName": "<string>",
"auth0Sub": "<string>",
"rolesByStep": {}
}
],
"questions": [{ "text": "<string>" }],
"cross_pollination": False,
"widgets_enabled": False,
"results_visibility": "host",
"distribution": [{ "group_id": "<string>" }]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
topic: '<string>',
goal: '<string>',
context: '<string>',
critical: '<string>',
prompt: '<string>',
template_id: '<string>',
project_id: '<string>',
roster: [
{
email: 'jsmith@example.com',
displayName: '<string>',
auth0Sub: '<string>',
rolesByStep: {}
}
],
questions: [{text: '<string>'}],
cross_pollination: false,
widgets_enabled: false,
results_visibility: 'host',
distribution: [{group_id: '<string>'}]
})
};
fetch('https://app.harmonica.chat/api/v1/sessions', 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://app.harmonica.chat/api/v1/sessions",
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([
'topic' => '<string>',
'goal' => '<string>',
'context' => '<string>',
'critical' => '<string>',
'prompt' => '<string>',
'template_id' => '<string>',
'project_id' => '<string>',
'roster' => [
[
'email' => 'jsmith@example.com',
'displayName' => '<string>',
'auth0Sub' => '<string>',
'rolesByStep' => [
]
]
],
'questions' => [
[
'text' => '<string>'
]
],
'cross_pollination' => false,
'widgets_enabled' => false,
'results_visibility' => 'host',
'distribution' => [
[
'group_id' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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://app.harmonica.chat/api/v1/sessions"
payload := strings.NewReader("{\n \"topic\": \"<string>\",\n \"goal\": \"<string>\",\n \"context\": \"<string>\",\n \"critical\": \"<string>\",\n \"prompt\": \"<string>\",\n \"template_id\": \"<string>\",\n \"project_id\": \"<string>\",\n \"roster\": [\n {\n \"email\": \"jsmith@example.com\",\n \"displayName\": \"<string>\",\n \"auth0Sub\": \"<string>\",\n \"rolesByStep\": {}\n }\n ],\n \"questions\": [\n {\n \"text\": \"<string>\"\n }\n ],\n \"cross_pollination\": false,\n \"widgets_enabled\": false,\n \"results_visibility\": \"host\",\n \"distribution\": [\n {\n \"group_id\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://app.harmonica.chat/api/v1/sessions")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"topic\": \"<string>\",\n \"goal\": \"<string>\",\n \"context\": \"<string>\",\n \"critical\": \"<string>\",\n \"prompt\": \"<string>\",\n \"template_id\": \"<string>\",\n \"project_id\": \"<string>\",\n \"roster\": [\n {\n \"email\": \"jsmith@example.com\",\n \"displayName\": \"<string>\",\n \"auth0Sub\": \"<string>\",\n \"rolesByStep\": {}\n }\n ],\n \"questions\": [\n {\n \"text\": \"<string>\"\n }\n ],\n \"cross_pollination\": false,\n \"widgets_enabled\": false,\n \"results_visibility\": \"host\",\n \"distribution\": [\n {\n \"group_id\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.harmonica.chat/api/v1/sessions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"topic\": \"<string>\",\n \"goal\": \"<string>\",\n \"context\": \"<string>\",\n \"critical\": \"<string>\",\n \"prompt\": \"<string>\",\n \"template_id\": \"<string>\",\n \"project_id\": \"<string>\",\n \"roster\": [\n {\n \"email\": \"jsmith@example.com\",\n \"displayName\": \"<string>\",\n \"auth0Sub\": \"<string>\",\n \"rolesByStep\": {}\n }\n ],\n \"questions\": [\n {\n \"text\": \"<string>\"\n }\n ],\n \"cross_pollination\": false,\n \"widgets_enabled\": false,\n \"results_visibility\": \"host\",\n \"distribution\": [\n {\n \"group_id\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"topic": "<string>",
"goal": "<string>",
"status": "active",
"participant_count": 123,
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"join_url": "<string>",
"chain": {
"status": "started",
"chainInstanceId": "<string>",
"stepId": "<string>",
"message": "<string>",
"reason": "<string>"
}
}{
"error": {
"code": "validation_error",
"message": "topic is required"
}
}{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key"
}
}Authorizations
API key authentication. Pass your key as a Bearer token.
Keys use the format hm_live_<32 hex chars>.
Generate keys from your Harmonica dashboard settings.
Body
Request body for creating a new session.
Session topic / title
What the session aims to achieve
Background context for participants (e.g. SESSION.md from a prior session)
Critical question or constraint
Custom facilitation prompt. If omitted, a prompt is auto-generated server-side from the topic, goal, context, and critical fields. Falls back to a basic facilitation prompt if generation fails.
ID of a template to use
Attach the new session to a project (workspace) at create time. Requires editor access to the project.
HAR-1582 - participants for a chain template's first step. Only meaningful when template_id resolves to a chain template; ignored otherwise.
Four of the live chain templates declare roles and an assignment strategy (Delphi panel, Governance Meeting, Many-to-Many Readiness, Example Mapping) and cannot bootstrap without a roster; for those, omitting it returns chain.status "roster_incomplete" on an otherwise successful create. The other seven declare no roles and start with an empty roster.
Show child attributes
Show child attributes
Pre-session questions shown to participants before facilitation begins
Show child attributes
Show child attributes
Enable cross-pollination of ideas between participant threads
Enable AI-emitted Polls and ratings widgets (SingleSelect, MultiSelect, RatingScale, RankingList) during the session. When true, the facilitator may call widget tools mid-conversation to present interactive inputs instead of plain text questions.
Who can see aggregated session results.
host(default) — only the session owner can see results.participants— anyone who completed the session can see what others said (drives the end-of-chat "See what others said" link).public— anyone with the session URL can see results without participating. MCP / harmonica-chat callers typically passparticipantssince programmatic and Telegram-distributed sessions usually want distributed visibility.
public, participants, host Distribution channels for the session (e.g. Telegram groups)
Show child attributes
Show child attributes
Response
Session created
Session object returned after creation. Extends SessionListItem with a join URL.
active Direct URL for participants to join the session
Outcome of chain bootstrap. Present only when template_id named a chain template. Any status other than started or resumed means the session was created and is real but is NOT running as a chain, so treat this field as the authoritative signal rather than assuming a 201 implies a running chain. The most common degraded status is roster_incomplete, because several chain templates declare roles and cannot start without a roster.
Show child attributes
Show child attributes