Reply to a preview comment
Appends a reply. Authorship comes from the authenticated principal.
Idempotent on the reply’s client_id: repeating a request appends
nothing and succeeds.
Set author_kind to agent to record the agent’s answer instead of a
person’s. A reply bound to the active run moves it from handed to
review, and leaves every other status where a person put it: the agent
can answer a thread but never close it, and never reopens one somebody
decided. Reopening or skipping a thread revokes its run; later replies
carrying that identity receive 409. While the run remains authorized,
repeating its answer stores nothing and succeeds, so the same body
cannot arrive twice under two ids.
An agent reply can be a question instead. With unfinished true the
reply is Mist saying it could not make the change without the author’s
answer, and a reply bound to the active run moves the thread from
handed to needs_info rather than review, handing it back to the
person who sent it. The author answers by reopening the thread and
sending it to Mist again. unfinished needs an agent reply with a
run_id; anywhere else it receives 422.
An agent reply bound to a run (run_id given) must come from the
person who started that run, the one who sent the comment to Mist.
From anyone else it receives 409 and stores nothing. A run started
before the starter was recorded accepts any authorized caller, as it
did before.
An agent reply is recorded with the person who posted it, in the
reply’s posted_by. An agent reply with no run_id needs a person
behind the request: a token with no person, such as a company or app
token, receives 403 and stores nothing. It is posted by that person’s
own chat, and the same text from two people is two replies, while one
person repeating it is one. A reply bound to a run is the run’s, so a
token with no person may post it only to a run started before its
starter was recorded, and has no posted_by.
Replying is discussion and is never refused for belonging to work. When the thread belongs to a work item, though, the run that owns the thread is the work item’s and not the comment’s: an answer carrying any other identity receives 409, and an answer that does land leaves the thread where it is, because a linked thread’s status belongs to the work item.
curl --request POST \
--url https://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"reply": {
"client_id": "r-5c20e9b4",
"body": "Agreed. Let's drop the font size a step below 768px rather than rewording it.",
"mentions": []
}
}
EOFimport requests
url = "https://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies"
payload = { "reply": {
"client_id": "r-5c20e9b4",
"body": "Agreed. Let's drop the font size a step below 768px rather than rewording it.",
"mentions": []
} }
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({
reply: {
client_id: 'r-5c20e9b4',
body: JSON.stringify('Agreed. Let\'s drop the font size a step below 768px rather than rewording it.'),
mentions: []
}
})
};
fetch('https://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies', 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.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies",
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([
'reply' => [
'client_id' => 'r-5c20e9b4',
'body' => 'Agreed. Let\'s drop the font size a step below 768px rather than rewording it.',
'mentions' => [
]
]
]),
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://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies"
payload := strings.NewReader("{\n \"reply\": {\n \"client_id\": \"r-5c20e9b4\",\n \"body\": \"Agreed. Let's drop the font size a step below 768px rather than rewording it.\",\n \"mentions\": []\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://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"reply\": {\n \"client_id\": \"r-5c20e9b4\",\n \"body\": \"Agreed. Let's drop the font size a step below 768px rather than rewording it.\",\n \"mentions\": []\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies")
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 \"reply\": {\n \"client_id\": \"r-5c20e9b4\",\n \"body\": \"Agreed. Let's drop the font size a step below 768px rather than rewording it.\",\n \"mentions\": []\n }\n}"
response = http.request(request)
puts response.read_body{
"preview_comment": {
"id": 31877,
"run_id": null,
"lock_version": 3,
"client_id": "c-3f9a2e71",
"n": 4,
"body": "The hero headline wraps onto three lines on mobile. Can we tighten it so it fits on two?",
"status": "open",
"status_at_ms": 1789398131000,
"page_url": "/",
"author_name": "Michael Scott",
"created_at_ms": 1789398131000,
"theme_changed_at": null,
"template_id": 902114,
"section": {
"id": "hero-banner-1",
"type": "hero-banner"
},
"block": null,
"target": {
"tag": "h1",
"selector": "section[data-section-id=\"hero-banner-1\"] h1.hero__title",
"text": "Limitless Paper in a Paperless World",
"html": "<h1 class=\"hero__title\">Limitless Paper in a Paperless World</h1>"
},
"pin": {
"x": 48.2,
"y": 22.7,
"ox": 0.41,
"oy": 0.63
},
"mentions": [
{
"id": "52018",
"name": "Pam Beesly"
}
],
"mentioned_project": null,
"proof_baseline": null,
"work_item": null,
"board_position": null,
"board_position_at_ms": null,
"assignee": null,
"assigner": null,
"assigned_at_ms": null,
"priority": null,
"priority_at_ms": null,
"replies": [
{
"client_id": "r-5c20e9b4",
"run_id": null,
"body": "Agreed. Let's drop the font size a step below 768px rather than rewording it.",
"author_name": "Pam Beesly",
"posted_by": null,
"created_at_ms": 1789398277000,
"mentions": [],
"shot": null,
"proof": null
}
]
},
"status": 201,
"meta": {
"request_uuid": "7c1e9a52-4b3d-4f0e-9d2a-81f6c3b5e240",
"timestamp": "2026-09-14T15:04:37Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}Authorizations
Path Parameters
Response
The comment, with the reply appended.
A single comment, wrapped in the standard response envelope.
curl --request POST \
--url https://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"reply": {
"client_id": "r-5c20e9b4",
"body": "Agreed. Let's drop the font size a step below 768px rather than rewording it.",
"mentions": []
}
}
EOFimport requests
url = "https://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies"
payload = { "reply": {
"client_id": "r-5c20e9b4",
"body": "Agreed. Let's drop the font size a step below 768px rather than rewording it.",
"mentions": []
} }
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({
reply: {
client_id: 'r-5c20e9b4',
body: JSON.stringify('Agreed. Let\'s drop the font size a step below 768px rather than rewording it.'),
mentions: []
}
})
};
fetch('https://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies', 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.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies",
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([
'reply' => [
'client_id' => 'r-5c20e9b4',
'body' => 'Agreed. Let\'s drop the font size a step below 768px rather than rewording it.',
'mentions' => [
]
]
]),
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://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies"
payload := strings.NewReader("{\n \"reply\": {\n \"client_id\": \"r-5c20e9b4\",\n \"body\": \"Agreed. Let's drop the font size a step below 768px rather than rewording it.\",\n \"mentions\": []\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://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"reply\": {\n \"client_id\": \"r-5c20e9b4\",\n \"body\": \"Agreed. Let's drop the font size a step below 768px rather than rewording it.\",\n \"mentions\": []\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/v202604/themes/{theme_id}/preview_comments/{client_id}/replies")
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 \"reply\": {\n \"client_id\": \"r-5c20e9b4\",\n \"body\": \"Agreed. Let's drop the font size a step below 768px rather than rewording it.\",\n \"mentions\": []\n }\n}"
response = http.request(request)
puts response.read_body{
"preview_comment": {
"id": 31877,
"run_id": null,
"lock_version": 3,
"client_id": "c-3f9a2e71",
"n": 4,
"body": "The hero headline wraps onto three lines on mobile. Can we tighten it so it fits on two?",
"status": "open",
"status_at_ms": 1789398131000,
"page_url": "/",
"author_name": "Michael Scott",
"created_at_ms": 1789398131000,
"theme_changed_at": null,
"template_id": 902114,
"section": {
"id": "hero-banner-1",
"type": "hero-banner"
},
"block": null,
"target": {
"tag": "h1",
"selector": "section[data-section-id=\"hero-banner-1\"] h1.hero__title",
"text": "Limitless Paper in a Paperless World",
"html": "<h1 class=\"hero__title\">Limitless Paper in a Paperless World</h1>"
},
"pin": {
"x": 48.2,
"y": 22.7,
"ox": 0.41,
"oy": 0.63
},
"mentions": [
{
"id": "52018",
"name": "Pam Beesly"
}
],
"mentioned_project": null,
"proof_baseline": null,
"work_item": null,
"board_position": null,
"board_position_at_ms": null,
"assignee": null,
"assigner": null,
"assigned_at_ms": null,
"priority": null,
"priority_at_ms": null,
"replies": [
{
"client_id": "r-5c20e9b4",
"run_id": null,
"body": "Agreed. Let's drop the font size a step below 768px rather than rewording it.",
"author_name": "Pam Beesly",
"posted_by": null,
"created_at_ms": 1789398277000,
"mentions": [],
"shot": null,
"proof": null
}
]
},
"status": 201,
"meta": {
"request_uuid": "7c1e9a52-4b3d-4f0e-9d2a-81f6c3b5e240",
"timestamp": "2026-09-14T15:04:37Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}{
"error": {
"message": "<string>",
"details": {}
},
"status": 123,
"meta": {
"request_uuid": "<string>",
"timestamp": "2023-11-07T05:31:56Z"
}
}