Comments API
Every Comments action (9) and data model (3): endpoints, scopes, input, preconditions, events and examples.
Notes and replies on any record, for your team.
Module code comments · 9 actions · 3 data models · 9 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: comments (everything), comments:read (read only) or comments:<capability>.
Resources
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
comment_mentions | comment_mention | /api/v1/comment_mentions | |||
comment_reactions | comment_reaction | /api/v1/comment_reactions | |||
comments | comment | /api/v1/comments | comments.delete |
Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.
Actions
| Action | Needs | What it does |
|---|---|---|
comments.delete | comments:read | Delete a comment |
comments.edit | comments:read | Edit a comment |
comments.post | comments:read | Comment on a record |
comments.publish | comments:read | Publish a comment |
comments.react | comments:read | React to a comment |
comments.reopen | comments:read | Reopen a thread |
comments.reply | comments:read | Reply to a comment |
comments.resolve | comments:read | Resolve a thread |
comments.unpublish | comments:read | Unpublish a comment |
comments.delete
Delete a comment — Removes a comment from the thread. Replies stay and become roots.
Needs comments:read · removes or cannot be undone · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/comments/{id}/actions/delete- REST alias:
DELETE /api/v1/comments/{id} - Canonical address:
POST /api/v1/actions/comments.delete
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Comment · id of comment |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
comment_not_found | The comment must exist. |
cannot_delete_comment | Removing somebody else’s comment needs delete on the target’s module. |
Events it publishes: comments.deleted
What it writes: comment (update) — Stamps deleted_at
curl -X POST https://app.getera.io/api/v1/comments/COMMENT_ID/actions/delete \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"comments.deleted"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.comments.delete("COMMENT_ID")era comments delete COMMENT_ID{
"name": "era__comments__delete",
"arguments": {
"id": "COMMENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 comment_not_found, cannot_delete_comment · 429 slow down
comments.edit
Edit a comment — Rewrites a comment you wrote, and says on screen that it changed.
Needs comments:read · changes data
https://app.getera.io/api/v1/comments/{id}/actions/edit- Canonical address:
POST /api/v1/actions/comments.edit
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Comment · id of comment |
body | string | yes | Comment · max 20000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
comment_not_found | The comment must exist. |
not_author | Only the author may change the words. |
empty_body | A comment must have words in it. |
mentioned_cannot_read | Nobody may be mentioned on a record they cannot open. |
Events it publishes: comments.edited, comments.mentioned
What it writes: comment (update) — Rewrites the body and stamps edited_at; comment_mention (insert) — Records anyone newly mentioned
curl -X POST https://app.getera.io/api/v1/comments/COMMENT_ID/actions/edit \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"body":"Comment"}'{
"ok": true,
"result": "…",
"events": [
"comments.edited",
"comments.mentioned"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.comments.edit("COMMENT_ID", {
body: "Comment"
})era comments edit COMMENT_ID \
--body "Comment"{
"name": "era__comments__edit",
"arguments": {
"id": "COMMENT_ID",
"body": "Comment"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 comment_not_found, not_author, empty_body, mentioned_cannot_read · 429 slow down
comments.post
Comment on a record — Adds a comment to a record. Internal until somebody publishes it.
Needs comments:read · changes data
https://app.getera.io/api/v1/comments/actions/post- Canonical address:
POST /api/v1/actions/comments.post
Input
| Field | Type | Required | Notes |
|---|---|---|---|
recordType | string | yes | Record type · max 60 |
recordId | uuid | yes | Record |
body | string | yes | Comment · max 20000 |
mentions | object / array | Mentions |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
unknown_record_type | The record type must be one a module owns. |
cannot_comment | Commenting needs the comment capability on the target’s module. |
empty_body | A comment must have words in it. |
record_not_found | The record must exist. |
mentioned_cannot_read | Nobody may be mentioned on a record they cannot open. |
Events it publishes: comments.posted, comments.mentioned
What it writes: comment (insert) — Writes the comment; comment_mention (insert) — Records who was mentioned
curl -X POST https://app.getera.io/api/v1/comments/actions/post \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"recordType":"Record type","recordId":"RECORD_ID","body":"Comment"}'{
"ok": true,
"result": "…",
"events": [
"comments.posted",
"comments.mentioned"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.comments.post({
recordType: "Record type",
recordId: "RECORD_ID",
body: "Comment"
})era comments post \
--record-type "Record type" \
--record-id "RECORD_ID" \
--body "Comment"{
"name": "era__comments__post",
"arguments": {
"recordType": "Record type",
"recordId": "RECORD_ID",
"body": "Comment"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 unknown_record_type, cannot_comment, empty_body, record_not_found, mentioned_cannot_read · 429 slow down
comments.publish
Publish a comment — Shows the comment on the storefront. Not something a default does.
Needs comments:read · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it
https://app.getera.io/api/v1/comments/{id}/actions/publish- Canonical address:
POST /api/v1/actions/comments.publish
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Comment · id of comment |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
comment_not_found | The comment must exist. |
cannot_manage_record | Publishing needs manage on the target’s module. |
is_a_reply | A reply is published with its thread. |
already_public | The comment must be private. |
Events it publishes: comments.published
What it writes: comment (update) — Sets is_public
curl -X POST https://app.getera.io/api/v1/comments/COMMENT_ID/actions/publish \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"comments.published"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.comments.publish("COMMENT_ID")era comments publish COMMENT_ID{
"name": "era__comments__publish",
"arguments": {
"id": "COMMENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 comment_not_found, cannot_manage_record, is_a_reply, already_public · 429 slow down
comments.react
React to a comment — Adds your reaction to a comment, or takes it back if you already gave it.
Needs comments:read · removes or cannot be undone
https://app.getera.io/api/v1/comment_reactions/actions/react- Canonical address:
POST /api/v1/actions/comments.react
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Comment · id of comment |
reaction | string | yes | Reaction · max 32 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
invalid_reaction | The reaction must be one of the eight offered. |
comment_not_found | The comment must exist. |
cannot_comment | Reacting needs the comment capability on the target’s module. |
Events it publishes: comments.reacted
What it writes: comment_reaction (insert) — Adds the reaction; comment_reaction (delete) — Takes a reaction back
curl -X POST https://app.getera.io/api/v1/comment_reactions/actions/react \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"id":"COMMENT_ID","reaction":"Reaction"}'{
"ok": true,
"result": "…",
"events": [
"comments.reacted"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.commentReactions.react({
id: "COMMENT_ID",
reaction: "Reaction"
})era comment_reactions react \
--id "COMMENT_ID" \
--reaction "Reaction"{
"name": "era__comments__react",
"arguments": {
"id": "COMMENT_ID",
"reaction": "Reaction"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 invalid_reaction, comment_not_found, cannot_comment · 429 slow down
comments.reopen
Reopen a thread — Puts a resolved thread back on the list.
Needs comments:read · changes data
https://app.getera.io/api/v1/comments/{id}/actions/reopen- Canonical address:
POST /api/v1/actions/comments.reopen
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Comment · id of comment |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
comment_not_found | The comment must exist. |
cannot_comment | Reopening needs the comment capability on the target’s module. |
not_resolved | The thread must be resolved. |
Events it publishes: comments.reopened
What it writes: comment (update) — Clears resolved_at
curl -X POST https://app.getera.io/api/v1/comments/COMMENT_ID/actions/reopen \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"comments.reopened"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.comments.reopen("COMMENT_ID")era comments reopen COMMENT_ID{
"name": "era__comments__reopen",
"arguments": {
"id": "COMMENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 comment_not_found, cannot_comment, not_resolved · 429 slow down
comments.reply
Reply to a comment — Answers a comment. Replies go one level deep and no further.
Needs comments:read · changes data
https://app.getera.io/api/v1/comments/{id}/actions/reply- Canonical address:
POST /api/v1/actions/comments.reply
Input
| Field | Type | Required | Notes |
|---|---|---|---|
parentId | uuid | yes | Filled from the path {id} · Comment · id of comment |
body | string | yes | Reply · max 20000 |
mentions | object / array | Mentions |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
parent_not_found | The comment being replied to must exist. |
parent_is_a_reply | A reply may not be replied to. |
cannot_comment | Replying needs the comment capability on the target’s module. |
empty_body | A reply must have words in it. |
mentioned_cannot_read | Nobody may be mentioned on a record they cannot open. |
Events it publishes: comments.posted, comments.mentioned
What it writes: comment (insert) — Writes the reply; comment_mention (insert) — Records who was mentioned
curl -X POST https://app.getera.io/api/v1/comments/COMMENT_ID/actions/reply \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"body":"Reply"}'{
"ok": true,
"result": "…",
"events": [
"comments.posted",
"comments.mentioned"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.comments.reply("COMMENT_ID", {
body: "Reply"
})era comments reply COMMENT_ID \
--body "Reply"{
"name": "era__comments__reply",
"arguments": {
"parentId": "COMMENT_ID",
"body": "Reply"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 parent_not_found, parent_is_a_reply, cannot_comment, empty_body, mentioned_cannot_read · 429 slow down
comments.resolve
Resolve a thread — Marks the thread as settled, so the panel is a list of what is left.
Needs comments:read · changes data
https://app.getera.io/api/v1/comments/{id}/actions/resolve- Canonical address:
POST /api/v1/actions/comments.resolve
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Comment · id of comment |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
comment_not_found | The comment must exist. |
cannot_comment | Resolving needs the comment capability on the target’s module. |
is_a_reply | A reply shares its thread’s state. |
already_resolved | The thread must be open. |
Events it publishes: comments.resolved
What it writes: comment (update) — Stamps resolved_at
curl -X POST https://app.getera.io/api/v1/comments/COMMENT_ID/actions/resolve \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"comments.resolved"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.comments.resolve("COMMENT_ID")era comments resolve COMMENT_ID{
"name": "era__comments__resolve",
"arguments": {
"id": "COMMENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 comment_not_found, cannot_comment, is_a_reply, already_resolved · 429 slow down
comments.unpublish
Unpublish a comment — Takes a published comment back off the storefront.
Needs comments:read · changes data
https://app.getera.io/api/v1/comments/{id}/actions/unpublish- Canonical address:
POST /api/v1/actions/comments.unpublish
Input
| Field | Type | Required | Notes |
|---|---|---|---|
id | uuid | yes | Filled from the path {id} · Comment · id of comment |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
comment_not_found | The comment must exist. |
cannot_manage_record | Unpublishing needs manage on the target’s module. |
already_private | The comment must be public. |
Events it publishes: comments.unpublished
What it writes: comment (update) — Clears is_public
curl -X POST https://app.getera.io/api/v1/comments/COMMENT_ID/actions/unpublish \
-H "Authorization: Bearer $ERA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{}'{
"ok": true,
"result": "…",
"events": [
"comments.unpublished"
]
}import { createEra } from "@getera/sdk"
const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.comments.unpublish("COMMENT_ID")era comments unpublish COMMENT_ID{
"name": "era__comments__unpublish",
"arguments": {
"id": "COMMENT_ID"
}
}Errors: 401 no or expired credential · 403 the credential does not hold comments:read · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 comment_not_found, cannot_manage_record, already_private · 429 slow down
Data models
CommentMention
Table comment_mention · Resource comment_mentions
GET /api/v1/comment_mentionsGET /api/v1/comment_mentions/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
comment_id | string (uuid) · uuid | comment | ||
user_id | string (uuid) · uuid | app_user | ||
created_at | string (date-time) · timestamptz |
Primary key: id
CommentReaction
Table comment_reaction · Resource comment_reactions
GET /api/v1/comment_reactionsGET /api/v1/comment_reactions/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
comment_id | string (uuid) · uuid | comment | ||
user_id | string (uuid) · uuid | app_user | ||
reaction | string · text | |||
created_at | string (date-time) · timestamptz |
Primary key: id
Unique: comment_id, user_id, reaction
Checks:
CHECK (((length(reaction) >= 1) AND (length(reaction) <= 32)))
Comment
Table comment · Resource comments
GET /api/v1/commentsGET /api/v1/comments/{id}DELETE /api/v1/comments/{id}→comments.delete
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
id | string (uuid) · uuid | |||
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
record_type | string · text | |||
record_id | string (uuid) · uuid | |||
body | string · text | |||
author_user_id | string (uuid) · uuid | yes | app_user | |
author_label | string · text | yes | ||
is_public | boolean · bool | |||
parent_id | string (uuid) · uuid | yes | comment | |
resolved_at | string (date-time) · timestamptz | yes | ||
resolved_by_user_id | string (uuid) · uuid | yes | app_user | |
edited_at | string (date-time) · timestamptz | yes | ||
deleted_at | string (date-time) · timestamptz | yes | ||
created_at | string (date-time) · timestamptz | |||
updated_at | string (date-time) · timestamptz |
Primary key: id
Checks:
CHECK ((length(btrim(body)) > 0))CHECK (((parent_id IS NULL) OR (parent_id <> id)))CHECK (((resolved_at IS NULL) OR (parent_id IS NULL)))CHECK (((resolved_at IS NULL) = (resolved_by_user_id IS NULL)))
Events
Workflows and webhooks react to these events.
| Event | What happens | Payload |
|---|---|---|
comments.deleted | A comment was removed from the thread. | id |
comments.edited | A comment was rewritten by its author. | id |
comments.mentioned | Somebody was mentioned in a comment. One event per newly mentioned person. | id, commentId, mentionedUserId, recordType, recordId, authorUserId |
comments.posted | A comment or reply was written. | id, recordType, recordId |
comments.published | A comment was shown on the storefront. | id |
comments.reacted | A reaction was added to, or taken back from, a comment. | id, reaction, added, authorUserId |
comments.reopened | A settled thread was reopened. | id |
comments.resolved | A thread was marked settled. | id |
comments.unpublished | A comment was taken off the storefront. | id |