The comment read and submit endpoints are part of Studio's public, versioned API (/api/comments/v1/), designed to be called directly from the site that renders the entry. Moderation, thread locking and import are part of the internal application API.
Studio-managed comments attach to content entries. Comments live in the Studio database (never in Git) and are served to any site through the public API. Per-model configuration lives on the model definition next to form, so it travels with the repository.
Comments are enabled per model with a comments block, saved through the model update route (workspace owner or admin). Only collection and document models can carry comments.
New comments start pending; false publishes immediately (needs comments.auto_approve, otherwise it stays true)
maxDepth
4
Reply nesting cap for new public submissions (0 = flat, max 10). Imports are never clamped.
requireEmail
true
Commenters must supply an email; it is never shown publicly
honeypot
true
Hidden _hp field; a filled honeypot is silently accepted and dropped
notifications
true
Email the workspace owners and admins when a comment arrives
captcha
null
'turnstile' to require a Cloudflare Turnstile token (needs comments.captcha, otherwise it is reset to null)
rateLimitPerIp
5
Submissions per IP per minute on one entry (1 to 60)
maxBodyLength
5000
Body length cap for public submissions (100 to 20000)
Errors: 403comments.upgrade (plan lacks comments.enabled, or the plan's model limit is reached), 400comments.not_collection (model kind cannot carry comments).
These endpoints require no authentication. Both return Access-Control-Allow-Origin: *, and the OPTIONS preflight is answered with 204 (the preflight allows only the Content-Type header, so do not send Authorization).
Pagination is over root comments; every approved reply under the page's roots is nested inside them.
Only approved comments are returned. A reply whose parent is not approved is not shown.
Email, IP address, user agent and referrer are never returned.
body is plain text; render it escaped. author.url is an http(s) address or null.
config.closed is true when the thread is locked: render the comments but hide the form.
config.captcha is "turnstile" only when the model asks for it and the plan includes comments.captcha; captchaSiteKey is null until the operator sets NUXT_PUBLIC_TURNSTILE_SITE_KEY.
The read endpoint is limited per IP (429comments.rate_limited).
status is approved when the model does not require approval (and the plan allows it), otherwise pending. Pending comments are only echoed back to their author.
Unless the model sets notifications: false, Studio emails the workspace owners and admins when a comment arrives. Plans with comments.webhook_notification also dispatch comment.submitted and comment.approvedwebhook events.
These endpoints require a session and a plan with comments.enabled (403comments.upgrade). Listing is open to workspace owners and admins and to members assigned to the project; every other route is limited to workspace owners and admins. A comment outside the project returns 404comments.comment_not_found.
POST /api/workspaces/:workspaceId/projects/:projectId/comments/:commentId/reply
Field
Type
Required
Description
body
string
Yes
Reply text (max 20000 characters)
The reply is published immediately and marked as a moderator reply (isModerator). Replying to a comment that is not yet approved approves it first. Returns the created reply.
A closed thread still shows its comments; the public submit endpoint refuses new ones with 403. Returns { modelId, entryId, locale, closed, closedAt }.
Import a contentrain-comments@1 export (the comments-export.json produced by the WordPress importer).
POST /api/workspaces/:workspaceId/projects/:projectId/comments/import
The body is the export document. The optional locale query parameter sets the default locale for entry references that carry none. Requires the comments.import plan feature (403comments.upgrade). Up to 5000 comments per request; send larger exports in chunks.
Every comment whose post maps to an entry is inserted with its source date, status, type and parent link, and reply depth is never clamped. Rows are keyed on their source ID, so re-sending an export is safe. A comment whose post has no entry mapping is reported in unmapped, never dropped silently.
Comments API
Public API
The comment read and submit endpoints are part of Studio's public, versioned API (
/api/comments/v1/), designed to be called directly from the site that renders the entry. Moderation, thread locking and import are part of the internal application API.Studio-managed comments attach to content entries. Comments live in the Studio database (never in Git) and are served to any site through the public API. Per-model configuration lives on the model definition next to
form, so it travels with the repository.Enabling Comments on a Model
Comments are enabled per model with a
commentsblock, saved through the model update route (workspace owner or admin). Only collection and document models can carry comments.enabledfalserequireApprovaltruepending;falsepublishes immediately (needscomments.auto_approve, otherwise it staystrue)maxDepth40= flat, max10). Imports are never clamped.requireEmailtruehoneypottrue_hpfield; a filled honeypot is silently accepted and droppednotificationstruecaptchanull'turnstile'to require a Cloudflare Turnstile token (needscomments.captcha, otherwise it is reset tonull)rateLimitPerIp5maxBodyLength5000Errors:
403comments.upgrade(plan lackscomments.enabled, or the plan's model limit is reached),400comments.not_collection(model kind cannot carry comments).Public Endpoints
These endpoints require no authentication. Both return
Access-Control-Allow-Origin: *, and theOPTIONSpreflight is answered with204(the preflight allows only theContent-Typeheader, so do not sendAuthorization).Read Comments
Get the approved comment threads of one entry, plus the configuration your embed needs.
localestringpagenumber1limitnumber20sortstringoldestoldestornewest(any other value meansoldest)Response
approvedcomments are returned. A reply whose parent is not approved is not shown.bodyis plain text; render it escaped.author.urlis anhttp(s)address ornull.config.closedistruewhen the thread is locked: render the comments but hide the form.config.captchais"turnstile"only when the model asks for it and the plan includescomments.captcha;captchaSiteKeyisnulluntil the operator setsNUXT_PUBLIC_TURNSTILE_SITE_KEY.The read endpoint is limited per IP (
429comments.rate_limited).Submit a Comment
Post a comment, or a reply to an approved comment.
Request Body
author.namestringauthor.emailstringrequireEmailis on; never shown publiclyauthor.urlstringhttporhttps)bodystringmaxBodyLength); HTML is strippedparentIdstringcaptchaTokenstring_hpstringResponse
Success:
statusisapprovedwhen the model does not require approval (and the plan allows it), otherwisepending. Pending comments are only echoed back to their author.Validation error (HTTP
200):Error
fieldvalues:author.name,author.email,author.url,body,parentId(missing, not found, or nested too deep) andcaptcha.Other Errors
403comments.enabled(also when the model is beyond the plan's model limit)404429rateLimitPerIp, per IP and entry), or the workspace's monthly comment quota402A filled honeypot returns
{ "success": true, "status": "pending" }without storing anything.Notifications and Webhooks
Unless the model sets
notifications: false, Studio emails the workspace owners and admins when a comment arrives. Plans withcomments.webhook_notificationalso dispatchcomment.submittedandcomment.approvedwebhook events.Plan Features
comments.enabledcomments.modelscomments.per_monthcomments.captchacomments.auto_approvecomments.importcomments.webhook_notificationSee Billing & Plans for the values per plan.
Moderation Endpoints
These endpoints require a session and a plan with
comments.enabled(403comments.upgrade). Listing is open to workspace owners and admins and to members assigned to the project; every other route is limited to workspace owners and admins. A comment outside the project returns404comments.comment_not_found.List Comments
statusstringpending,approved,rejected,spammodelIdstringentryIdstringlocalestringpagenumber1limitnumber50100)sortstringnewestnewestoroldestReturns
{ comments, total, counts, page, limit }, wherecountsholds the number of comments per status.Update Comment Status
status'approved' | 'rejected' | 'spam' | 'pending'Returns the updated comment.
400comments.invalid_statusfor any other value.Delete Comment
Replies are deleted with the comment. Returns
{ "deleted": true }.Reply as Moderator
bodystringThe reply is published immediately and marked as a moderator reply (
isModerator). Replying to a comment that is not yet approved approves it first. Returns the created reply.Bulk Update
action'approve' | 'reject' | 'spam' | 'pending' | 'delete'commentIdsstring[]400comments.bulk_limit)deletereturns{ "action", "results": [{ "id", "success", "error?" }], "succeeded": 3 }and writes an audit log entry per comment; the other actions return{ "action", "updated": 3 }.Open or Close a Thread
closedbooleantrueto close,falseto reopenlocalestringA closed thread still shows its comments; the public submit endpoint refuses new ones with
403. Returns{ modelId, entryId, locale, closed, closedAt }.Import WordPress Comments
Import a
contentrain-comments@1export (thecomments-export.jsonproduced by the WordPress importer).The body is the export document. The optional
localequery parameter sets the default locale for entry references that carry none. Requires thecomments.importplan feature (403comments.upgrade). Up to 5000 comments per request; send larger exports in chunks.Every comment whose post maps to an entry is inserted with its source date, status, type and parent link, and reply depth is never clamped. Rows are keyed on their source ID, so re-sending an export is safe. A comment whose post has no entry mapping is reported in
unmapped, never dropped silently.Related Pages
comment.submittedandcomment.approvedevents