Create Comment
curl --request POST \
--url https://api.sublay.io/v8/:projectId/comments \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"entityId": "<string>",
"content": "<string>",
"gif": {},
"attachments": [
{}
],
"parentId": "<string>",
"referencedCommentId": "<string>",
"foreignId": "<string>",
"mentions": [
{}
],
"metadata": {},
"userId": "<string>",
"createdAt": "<string>",
"updatedAt": "<string>"
}
'import requests
url = "https://api.sublay.io/v8/:projectId/comments"
payload = {
"entityId": "<string>",
"content": "<string>",
"gif": {},
"attachments": [{}],
"parentId": "<string>",
"referencedCommentId": "<string>",
"foreignId": "<string>",
"mentions": [{}],
"metadata": {},
"userId": "<string>",
"createdAt": "<string>",
"updatedAt": "<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({
entityId: '<string>',
content: '<string>',
gif: {},
attachments: [{}],
parentId: '<string>',
referencedCommentId: '<string>',
foreignId: '<string>',
mentions: [{}],
metadata: {},
userId: '<string>',
createdAt: '<string>',
updatedAt: '<string>'
})
};
fetch('https://api.sublay.io/v8/:projectId/comments', 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.sublay.io/v8/:projectId/comments",
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([
'entityId' => '<string>',
'content' => '<string>',
'gif' => [
],
'attachments' => [
[
]
],
'parentId' => '<string>',
'referencedCommentId' => '<string>',
'foreignId' => '<string>',
'mentions' => [
[
]
],
'metadata' => [
],
'userId' => '<string>',
'createdAt' => '<string>',
'updatedAt' => '<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://api.sublay.io/v8/:projectId/comments"
payload := strings.NewReader("{\n \"entityId\": \"<string>\",\n \"content\": \"<string>\",\n \"gif\": {},\n \"attachments\": [\n {}\n ],\n \"parentId\": \"<string>\",\n \"referencedCommentId\": \"<string>\",\n \"foreignId\": \"<string>\",\n \"mentions\": [\n {}\n ],\n \"metadata\": {},\n \"userId\": \"<string>\",\n \"createdAt\": \"<string>\",\n \"updatedAt\": \"<string>\"\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.sublay.io/v8/:projectId/comments")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"entityId\": \"<string>\",\n \"content\": \"<string>\",\n \"gif\": {},\n \"attachments\": [\n {}\n ],\n \"parentId\": \"<string>\",\n \"referencedCommentId\": \"<string>\",\n \"foreignId\": \"<string>\",\n \"mentions\": [\n {}\n ],\n \"metadata\": {},\n \"userId\": \"<string>\",\n \"createdAt\": \"<string>\",\n \"updatedAt\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sublay.io/v8/:projectId/comments")
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 \"entityId\": \"<string>\",\n \"content\": \"<string>\",\n \"gif\": {},\n \"attachments\": [\n {}\n ],\n \"parentId\": \"<string>\",\n \"referencedCommentId\": \"<string>\",\n \"foreignId\": \"<string>\",\n \"mentions\": [\n {}\n ],\n \"metadata\": {},\n \"userId\": \"<string>\",\n \"createdAt\": \"<string>\",\n \"updatedAt\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyComment Endpoints
Create Comment
Create a new comment or reply on an entity
Create Comment
curl --request POST \
--url https://api.sublay.io/v8/:projectId/comments \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"entityId": "<string>",
"content": "<string>",
"gif": {},
"attachments": [
{}
],
"parentId": "<string>",
"referencedCommentId": "<string>",
"foreignId": "<string>",
"mentions": [
{}
],
"metadata": {},
"userId": "<string>",
"createdAt": "<string>",
"updatedAt": "<string>"
}
'import requests
url = "https://api.sublay.io/v8/:projectId/comments"
payload = {
"entityId": "<string>",
"content": "<string>",
"gif": {},
"attachments": [{}],
"parentId": "<string>",
"referencedCommentId": "<string>",
"foreignId": "<string>",
"mentions": [{}],
"metadata": {},
"userId": "<string>",
"createdAt": "<string>",
"updatedAt": "<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({
entityId: '<string>',
content: '<string>',
gif: {},
attachments: [{}],
parentId: '<string>',
referencedCommentId: '<string>',
foreignId: '<string>',
mentions: [{}],
metadata: {},
userId: '<string>',
createdAt: '<string>',
updatedAt: '<string>'
})
};
fetch('https://api.sublay.io/v8/:projectId/comments', 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.sublay.io/v8/:projectId/comments",
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([
'entityId' => '<string>',
'content' => '<string>',
'gif' => [
],
'attachments' => [
[
]
],
'parentId' => '<string>',
'referencedCommentId' => '<string>',
'foreignId' => '<string>',
'mentions' => [
[
]
],
'metadata' => [
],
'userId' => '<string>',
'createdAt' => '<string>',
'updatedAt' => '<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://api.sublay.io/v8/:projectId/comments"
payload := strings.NewReader("{\n \"entityId\": \"<string>\",\n \"content\": \"<string>\",\n \"gif\": {},\n \"attachments\": [\n {}\n ],\n \"parentId\": \"<string>\",\n \"referencedCommentId\": \"<string>\",\n \"foreignId\": \"<string>\",\n \"mentions\": [\n {}\n ],\n \"metadata\": {},\n \"userId\": \"<string>\",\n \"createdAt\": \"<string>\",\n \"updatedAt\": \"<string>\"\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.sublay.io/v8/:projectId/comments")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"entityId\": \"<string>\",\n \"content\": \"<string>\",\n \"gif\": {},\n \"attachments\": [\n {}\n ],\n \"parentId\": \"<string>\",\n \"referencedCommentId\": \"<string>\",\n \"foreignId\": \"<string>\",\n \"mentions\": [\n {}\n ],\n \"metadata\": {},\n \"userId\": \"<string>\",\n \"createdAt\": \"<string>\",\n \"updatedAt\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sublay.io/v8/:projectId/comments")
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 \"entityId\": \"<string>\",\n \"content\": \"<string>\",\n \"gif\": {},\n \"attachments\": [\n {}\n ],\n \"parentId\": \"<string>\",\n \"referencedCommentId\": \"<string>\",\n \"foreignId\": \"<string>\",\n \"mentions\": [\n {}\n ],\n \"metadata\": {},\n \"userId\": \"<string>\",\n \"createdAt\": \"<string>\",\n \"updatedAt\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyCreates a new comment on an entity, or a reply to an existing comment. The comment is attributed to the authenticated user. Notifications are sent to the entity owner (for a top-level comment), the parent comment’s author (for a reply), and mentioned users.
Authentication required.
This applies to everyone, including the author of a removed comment, who still sees it in full. A comment that is both deleted and removed returns the deleted code. When the parent and the quoted comment are both refused, the response is the parent’s.
For replies, the rule covers the direct parent only. Replies that already sit under a deleted or removed comment can still be replied to, and quoted.
These responses are only returned for a comment the commenting user can see. A deleted or removed comment that a block hides from them (see Blocks) returns
Body Parameters
string
required
The UUID of the entity this comment belongs to.
string
The text content of the comment. At least one of
content, gif, or attachments must be provided.object
A GIF to attach to the comment. At least one of
content, gif, or attachments must be provided.{
"id": "gif-id",
"altText": "laughing cat",
"gifUrl": "https://media.giphy.com/...",
"gifPreviewUrl": "https://media.giphy.com/...",
"url": "https://giphy.com/...",
"aspectRatio": "1.5"
}
aspectRatio is width ÷ height as a decimal string. Colon notation like "16:9" is rejected by clients that compute layout from it.object[]
An array of generic attachment objects. At least one of
content, gif, or attachments must be provided.string
The UUID of the parent comment. When provided, this comment is treated as a reply to that comment. The parent comment must be on the entity given in
entityId — otherwise the request returns 404 comment/entity-not-found. The parent comment’s author receives a reply notification.The parent comment can’t be one its author deleted (403 comment/parent-deleted) or one a moderator removed (403 comment/parent-removed) — see Deleted and removed parents and quotes.string
The UUID of a comment being quoted or referenced. Used for quote-replies. The quoted comment may be on any entity, but it must be visible to the commenting user — otherwise the request returns
404 comment/entity-not-found.The quoted comment can’t be one its author deleted (403 comment/referenced-deleted) or one a moderator removed (403 comment/referenced-removed) — see Deleted and removed parents and quotes.string
Your application’s identifier for this comment. Must be unique across the project’s comments — a
foreignId already in use returns 409 comment/foreign-id-taken. A foreignId stays reserved after its comment is deleted: the deleted comment remains in its thread as a placeholder and is still found by that foreignId.object[]
Users or spaces mentioned in the comment. Each mentioned user receives one in-app notification, however many times they are mentioned, except the person the comment already notifies: the entity owner for a top-level comment, or the parent comment’s author for a reply. A space mention sends no notification, and a commenter mentioning themselves is not notified.
{ type: "user", id: string, username: string, foreignId?: string }{ type: "space", id: string, slug: string }
object
A free-form JSON object for any custom data your application needs alongside the comment. Up to 10 KB (10,240 bytes) as JSON — a larger object returns
400 comment/invalid-body.string
Attribute the comment to a specific user. Service or master key only, and required with one — a key has no user of its own, so leaving it out returns
400 comment/missing-user-id. Useful for backfilling comments from external systems. A user token is always its own user: naming a different user returns 403 comment/unauthorized.A userId that doesn’t match a user returns 404 comment/user-not-found.string
Datetime to backfill a creation timestamp. Service or master key only — with a user token the value is ignored and the comment gets the current time.Must be a UTC ISO 8601 datetime ending in
Z, such as 2026-01-15T09:30:00Z. A value with an offset (+02:00) or a date without a time returns 400 comment/invalid-body (createdAt: Invalid ISO datetime), whichever credential sends it.string
Datetime for
updatedAt, in the same format as createdAt — UTC ISO 8601 ending in Z; anything else returns 400 comment/invalid-body. Service or master key only — with a user token the value is ignored.Requires createdAt, and must be the same as or later than it; otherwise the request returns 400 comment/invalid-timestamp. When only createdAt is sent, updatedAt is set to the same time.Blocks
When themoderation bundle is installed, three people are checked: the entity’s author, the parent comment’s author (for replies), and the quoted comment’s author (with referencedCommentId).
- If the commenting user blocked any of them, the comment is allowed — the block is their own choice. The blocked person isn’t notified.
- If any of them blocked the commenting user (or both blocked each other), the request is refused with
404 comment/entity-not-found— the same response as a missing entity, so it never reveals a block.
isUnavailable can’t be replied to. Commenting can also be refused when the entity’s author has blocked the commenting user, which no comment flag shows.
Deleted and removed parents and quotes
A comment its author deleted, or a moderator removed, can’t be replied to or quoted:| The comment was | As parentId | As referencedCommentId |
|---|---|---|
| Deleted by its author | 403 comment/parent-deleted | 403 comment/referenced-deleted |
| Removed by a moderator | 403 comment/parent-removed | 403 comment/referenced-removed |
404 comment/entity-not-found, like any other.
Response
Returns201 Created with the new Comment object, populated with the author’s user profile.
Error Responses
Entity Not Found — 404
Entity Not Found — 404
Returned when the entity doesn’t exist, when
parentId doesn’t exist or belongs to another entity, when the quoted comment isn’t visible to the commenting user, or when a block refuses the comment (see Blocks).{ "error": "Entity not found", "code": "comment/entity-not-found" }
Missing User — 400
Missing User — 400
Returned when a service or master key sends no
userId.{ "error": "Missing user ID", "code": "comment/missing-user-id" }
Missing token — 401
Missing token — 401
No
Authorization header was sent. The body is text/plain (Unauthorized), not JSON. An expired or invalid token returns 403 instead — see Authentication.Invalid Body — 400
Invalid Body — 400
Returned when the body fails validation.
error names the field and the problem:- none of
content,giforattachmentsis provided —contentthat is empty or only whitespace, and an emptyattachmentsarray, don’t count; entityId,parentId,referencedCommentIdoruserIdisn’t a UUID (a missingentityIdincluded);gifis missing one of its six fields, or one has the wrong type;- a
mentionsitem doesn’t match one of the two shapes; foreignIdis an empty string or longer than 255 characters;metadataisn’t an object, or is larger than 10 KB;createdAtorupdatedAtisn’t a UTC ISO 8601 datetime ending inZ.
{ "error": "At least one of content, gif, or attachments must be provided", "code": "comment/invalid-body" }
{ "error": "entityId: Invalid UUID format", "code": "comment/invalid-body" }
Acting as another user — 403
Acting as another user — 403
Returned when a request authenticated with a user token sends a
userId other than that user’s own ID.{ "error": "Unauthorized", "code": "comment/unauthorized" }
User Not Found — 404
User Not Found — 404
Returned when a service or master key names a
userId that doesn’t exist.{ "error": "User not found", "code": "comment/user-not-found" }
Invalid Timestamp — 400
Invalid Timestamp — 400
Returned to a service or master key when
updatedAt is sent without createdAt, or is earlier than it.{ "error": "Cannot set updatedAt without also setting createdAt.", "code": "comment/invalid-timestamp" }
{ "error": "updatedAt must be the same or after createdAt.", "code": "comment/invalid-timestamp" }
Rejected by Webhook — 400
Rejected by Webhook — 400
Returned when your project’s
comment.created validation webhook doesn’t approve the comment. Nothing is created. error is the message your webhook responded with, when it sent one.{ "error": "Validation rejected", "code": "comment/webhook-validation-failed" }
Parent Deleted — 403
Parent Deleted — 403
Returned when
parentId is a comment its author deleted (see Deleted and removed parents and quotes).{ "error": "This comment was deleted and can't be replied to.", "code": "comment/parent-deleted" }
Parent Removed — 403
Parent Removed — 403
Returned when
parentId is a comment a moderator removed — for every requester, including that comment’s author (see Deleted and removed parents and quotes).{ "error": "This comment was removed and can't be replied to.", "code": "comment/parent-removed" }
Quoted Comment Deleted — 403
Quoted Comment Deleted — 403
Returned when
referencedCommentId is a comment its author deleted (see Deleted and removed parents and quotes).{ "error": "This comment was deleted and can't be quoted.", "code": "comment/referenced-deleted" }
Quoted Comment Removed — 403
Quoted Comment Removed — 403
Returned when
referencedCommentId is a comment a moderator removed — for every requester, including that comment’s author (see Deleted and removed parents and quotes).{ "error": "This comment was removed and can't be quoted.", "code": "comment/referenced-removed" }
Foreign ID Taken — 409
Foreign ID Taken — 409
Returned when another comment already uses the supplied
foreignId.{ "error": "A comment with this foreignId already exists.", "code": "comment/foreign-id-taken" }

