Comments

Comments are what people write under a post or an event, and the replies to them. These endpoints move the comments of the old platform in, with their original dates and authors, and let you correct or remove what you imported. Read Start here first: its rules apply to everything on this page. Comments work like Posts and Events, and this page says where they differ.

Import the posts and events first. A comment goes on a post or an event that already exists in Zen Social, and a reply goes on a comment that already exists, so a move runs in this order: posts and events, then comments, parents before their replies. Keep your own list of the id each external_id was given.

What the API has, and what it does not

  • Comments on posts and on events, replies included.
  • Not in the API: pictures on comments (the app's comment form has none either) and comments on newsletter editions. A comment on an edition is not listed, and GET, PATCH and DELETE answer 404 not_found for it.

The comment object

{
  "id": 9201,
  "external_id": "fb-comment-8001",
  "post_id": 9001,
  "event_id": null,
  "parent_id": null,
  "agenda_item_id": null,
  "author": {"id": "EveABqLyto9L", "name": "Karen Holm"},
  "content": "I can bring basil seedlings on Saturday.",
  "hidden": false,
  "deleted": false,
  "created_at": "2023-04-15T12:30:00.000Z",
  "edited_at": null
}
Field Type Meaning
id number The comment's id. Use it in /comments/:id, and as parent_id for a reply.
external_id text or null Your id from the old platform, when the comment was created through the API with one. null for every other comment.
post_id number or null The post the comment is on.
event_id number or null The event the comment is on. Exactly one of post_id and event_id is set. A reply carries the post or event of the whole thread, however deep it is.
parent_id number or null The comment this one replies to. null for a comment directly on the post or event.
agenda_item_id number or null For a comment on a meeting: the agenda item it was written under, in the app. A reply carries the item of the comment it replies to, also when you create it here. null for a comment in the event's own thread, and for every comment on a post. The API cannot put a comment under an item.
author object or null Who wrote it, in the same two shapes as a post's author (below). null for a hidden comment.
content text The text, as plain text. Empty ("") for a deleted or a hidden comment.
hidden true or false true when the comment is the app's placeholder for you: see "Hidden authors" below.
deleted true or false true for a comment that has been deleted: see "Deleted comments" below.
created_at time When it was written: the original date for an imported comment.
edited_at time or null When a person last edited it in the app. Changes through the API never set it.

author is one of two shapes:

  • A member: {"id": "EveABqLyto9L", "name": "Karen Holm"}. The id is the member's id from Members.
  • A name from the old platform, for someone who has no Zen Social account (yet): {"id": null, "name": "Ole Berg", "external_id": "fb-user-1002"}. The external_id is your own id for that person, as you sent it; null if the comment was created without an external_id of its own. See Authors and claims.

Comments go with the account of their author when it is deleted. The one kept comment is on a meeting's agenda: it shows "id": null and the name the account had, or "name": null when it had none.

There are no email addresses and no picture addresses.

List them: GET /comments

Comments are listed for one post or one event at a time. Give post_id or event_id, and not both:

curl "$API/comments?post_id=9001" -H "Authorization: Bearer $KEY"
curl "$API/comments?event_id=9101" -H "Authorization: Bearer $KEY"

Answers 200 OK with {"data": [<comment>, …], "next_page": null}, 100 per page (?page=2 for the next). The newest comment comes first, and comments with the same time by their id, highest first. All the comments of the post or event come in one list, replies included, not nested: use parent_id to build the thread. A reply to a comment on another page is on the same list, so read every page before you nest.

A comment is listed exactly as the admin would see it in the app, and nothing more:

  • 404 not_found when the post or event does not exist, is not in one of this community's circles, has been deleted, or is one the admin could not see in the app. (Here it is never 403.)
  • Deleted comments are in the list (below).
  • Comments of authors the admin has blocked, or who have blocked the admin, are in the list as placeholders (below).

422 invalid when neither post_id nor event_id is given, or both.

Deleted comments

A deleted comment stays in the list as a tombstone: "deleted": true and "content": "". Its id, parent_id, dates and author stay, so its replies still have a place in the thread. GET /comments/:id answers 200 OK with the tombstone too.

Hidden authors

When the admin has blocked the author, or the author has blocked the admin, the app shows the comment as a grey placeholder, and keeps its replies. The API gives the same placeholder: "hidden": true, "content": "" and "author": null, with the rest as it is. A comment that is both hidden and deleted also has "author": null.

The placeholder follows the admin who made the key: another admin, with no block, sees the comment itself.

A block does not stop a PATCH or a DELETE: the rules in "Who may change which comment" apply as without it. The PATCH changes the text, and answers with the placeholder ("content": ""), not with the text you sent.

Read one: GET /comments/:id

curl "$API/comments/9201" -H "Authorization: Bearer $KEY"

Answers 200 OK with the comment, also when it is deleted or hidden. 404 not_found when it is not on a post or event in one of this community's circles, or is on one that has been deleted or that the admin could not see in the app.

Create one: POST /comments

The first example is a comment on a post under a member. The second is a reply to it, under a name from the old platform:

curl "$API/comments" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"external_id": "fb-comment-8001", "post_id": 9001, "author_id": "EveABqLyto9L", "created_at": "2023-04-15T12:30:00Z", "content": "I can bring basil seedlings on Saturday."}'
curl "$API/comments" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"external_id": "fb-comment-8002", "post_id": 9001, "parent_id": 9201, "author": {"external_id": "fb-user-1002", "name": "Ole Berg"}, "created_at": "2023-04-15T18:05:00Z", "content": "Saturday is fine. I will keep the table free."}'

A comment on an event takes event_id instead of post_id:

curl "$API/comments" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"external_id": "fb-comment-8003", "event_id": 9101, "author_id": "EveABqLyto9L", "created_at": "2026-10-21T09:00:00Z", "content": "Is there parking near the garden hut?"}'
Field Rules
post_id or event_id Required: exactly one of them, the id of the post or event the comment goes on. It must be in a circle of this community that the admin can see, and the admin must be able to comment in every circle it is in: 403 forbidden otherwise. 404 not_found for one that does not exist, is in no circle of this community, is deleted, or that the admin cannot see.
content Required: plain text, not empty. The same rules as a post's text: see The text. Comments have no word limit.
parent_id The id of the comment this one replies to. Optional: without it the comment goes directly on the post or event. See "Replies".
external_id Your id for this comment on the old platform, as text or a number, at most 200 characters. Always send it: re-runs, changing the comment later and handing it back to its author all depend on it (see "Who may change which comment").
author_id A member's id from Members. They must be a member of every circle the post or event is in.
author {"external_id": "…", "name": "…"} for someone who has not joined. The same rules as for a post: see Who wrote it.
created_at The original date and time, ISO 8601, in the past and no earlier than 1 January 1990. The same rules as for a post: see The date. Default: now.
notify true to tell people about the comment the way the app would. Default false. See "Telling people".

With neither author_id nor author, the admin who made the key is the author. Fields the API does not know are ignored without an error, except images and image_urls: comments take no pictures, so those are refused (below).

Answers:

  • 201 Created with the new comment.
  • 200 OK, 409 record_deleted, 409 changed_since_import, 409 conflict or 403 forbidden when the external_id was imported before: see "Re-runs" below.
  • 403 forbidden: the admin cannot comment in one of the circles of the post or event.
  • 404 not_found: the post or event is not found (above), also when it has been deleted.
  • 422 invalid: a value is not allowed; details names each field and why, for example {"content": ["is required"]}. Nothing was made. This includes images and image_urls ({"images": ["comments take no images"]}), and a parent_id that cannot be used (below).
  • 422 reply_to_another_thread, 422 reply_too_deep, 422 reply_to_deleted and 422 not_accepting_comments: see "Replies". Nothing was made.
  • 422 notify_needs_member_author: see "Telling people". Nothing was made.

The checks run in this order, and the first that fails answers: 422 for a malformed external_id, then a re-run if the external_id is known (it answers as in "Re-runs", and the checks below are not made), then 422 for a missing, doubled or malformed post_id or event_id, 404 for the post or event, 422 not_accepting_comments (a cancelled event), 403 for a circle the admin cannot comment in, then the other 422 answers. On a cancelled event, an admin who also cannot comment gets the 422, not the 403.

Replies

Send parent_id to make a reply. The reply goes in the thread of its parent and shows under it. The rules are those of the app's Reply link:

  • The parent must be on the same post or event. A parent on another one is 422 {"error":"reply_to_another_thread"}.
  • The parent must not be deleted: 422 {"error":"reply_to_deleted"}. A reply to a comment that has replies of its own is fine.
  • Replies nest at most 9 levels deep. A comment directly on the post or event is level 0, its reply level 1, and so on down to level 9. A reply to a comment that is already at level 9 is 422 {"error":"reply_too_deep"}. If the old platform allows deeper threads, attach the deepest replies to the last comment that can take them, and tell the admin.
  • The post or event must still take comments: a cancelled event does not, and the answer is 422 {"error":"not_accepting_comments"}. A comment that is not a reply gets the same answer. A deleted post or event is not found at all: 404 not_found.
  • A parent_id that is not an id of a comment, or is one the admin cannot read (it does not exist, it is in another community, it is on a post or event the admin cannot see), answers 422 invalid with details.parent_id. The two causes give exactly the same answer on purpose: the API does not tell you which ids exist elsewhere.

You give the post or event as well as the parent. The parent has to be a comment on that same post or event.

No pictures

images and image_urls are refused with 422 invalid, under the field that was sent. They are not ignored, so a comment is never moved without its picture by accident. If you decide to move such a comment without the picture, leave the two fields out, and tell the admin which comments lost a picture.

Telling people: notify

By default nothing tells anyone about a comment you create (see Silent by default).

"notify": true makes the create announce the comment exactly as the app does when a member comments: notifications and push messages, and lines in daily summaries, each according to people's own notification settings. Send it only when the admin has asked for people to be told, and never for imported history. It announces the comment once, when it is created; a re-run never announces it again.

A comment with notify needs a real member as its author (author_id, or neither field): a name from the old platform cannot speak to anyone. With author the answer is 422 {"error":"notify_needs_member_author"}. Its author also needs a complete profile, as in the app (422 invalid, with the reason under details.base).

Re-runs

Send a create again with the same external_id, and the comment is not made twice. The answers are those of Re-runs and external_id, the same as for posts and events:

Answer Meaning
200 OK The comment was updated and keeps its id.
409 record_deleted The comment was deleted (in the app or through the API), or the post or event it is on was deleted. It is never brought back. Skip it and report it.
409 changed_since_import Someone edited the comment in the app since your last write. It was not overwritten. Skip it and report it. A reply to it, or a claim, does not count as an edit.
409 conflict The same create arrived twice at once. Send it again, once.
403 forbidden The admin can no longer comment in one of the circles of the post or event. Skip it and report it.
422 invalid The content sent is not allowed, or images or image_urls were sent (see "Create one"); details says why. Nothing was changed.

There is no 409 in_edition for comments: a newsletter edition does not lock a comment.

A re-run changes only content, and only when it is sent. The post or event, the parent, the author, created_at and notify are ignored: a re-run does not even check them, and never tells anyone. To change any of those, delete the comment (with the admin's yes) and create it under a new external_id.

An edit in the app counts, even a quiet one. When a person opens an imported comment in the editor and saves it without changing a word, that is an edit too: pressing Save is what counts, not whether the text changed. The next re-run answers 409 changed_since_import for it, and the comment stays as the person left it.

A post that was turned into an event in the app keeps its comments. A re-run of one of its comments answers 200 OK, and the comment now has event_id set and post_id null. A re-run ignores the post_id or event_id you send.

Change one: PATCH /comments/:id

Send content, with the same rules as for a create. Nothing else can be changed: other fields are ignored. Without content the answer is 200 OK and nothing changes.

curl -X PATCH "$API/comments/9201" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"content": "I can bring basil and chive seedlings on Saturday."}'

Before a PATCH of a comment whose author is now a member (its author has an id), or whose edited_at is not null, show the admin the change (the comment as it is and as it will be) and wait for their yes. Those may be a person's own words, written or corrected by them.

Answers 200 OK with the comment, 422 invalid with the reasons (an empty content, or images; and, for a comment not created through the API with an external_id, an author who has not completed their profile in the app, under details.base), 403 forbidden when the admin may not change this comment (see "Who may change which comment"), or 404 not_found. A deleted comment answers 404 not_found: it cannot be changed, and it is not brought back.

A change through the API is not an edit by a person: the comment keeps its date and shows no "(Edited)" mark, and nobody is told. A later re-run of its create still updates it. A comment someone edited in the app is different: it keeps that mark after your PATCH, so a later re-run still answers 409 changed_since_import.

Delete one: DELETE /comments/:id

Warning: this deletes the comment for everyone. It is replaced by a deleted comment in the thread. There is no undo, and a re-run never brings it back.

Never send a DELETE without the admin's clear yes. Tell them which comment it is (its author, date and the start of its text) and wait for their answer. Only this comment is deleted: its replies stay, under the deleted comment, as in the app.

curl -X DELETE "$API/comments/9201" -H "Authorization: Bearer $KEY"

Answers 204 No Content. 403 forbidden when the admin may not delete it, 404 not_found when it is not this community's. A second DELETE of the same comment answers 404 not_found. A later create with its external_id answers 409 record_deleted. Nobody is told.

Who may change which comment

Comments you imported: a comment created through this API with an external_id may be re-run, changed, deleted and handed to its author through the API, whoever its author is now, as long as the admin can still comment in every circle of its post or event. That holds for any admin's key in this community, and after a claim has made a member its author. This is the steward right: whoever moves the content in can still correct it.

Every other comment follows the app's rules, as if the admin did it in the app: only its author can change it (the admin's own comments, and comments created through the API without an external_id and without an author), and its author or an owner or admin of one of the circles of its post or event can delete it. A comment created without an external_id under another member's author_id or under a name from the old platform has no record in the API. You cannot PATCH it afterwards (403 forbidden), and you can delete it only with the rights the app gives: as its author, or as an owner or admin of one of the circles. A comment under a name can never be claimed either. Send an external_id for every comment you may want to change later.

The steward right exists only in this API. In the app, an imported comment follows the usual rules: a member author can edit it like any comment of theirs, and a comment under a name from the old platform cannot be edited by anyone there.

Nobody is told

Creating, re-running, changing, deleting and claiming comments tells no member anything, unless a create sends "notify": true. Imported comments are not announced later either, and the post or event they are on does not move up in the circle's activity: a comment you add to an old post leaves the post where it was in the feed.