Comments
List, add and delete comments on Goals, Objectives, Meetings, Actions and Ideas.
Comments belong to a parent record: a Goal, Objective, Meeting, Action or Idea. Each parent has the same three operations.
| Method | Path | Scope |
|---|---|---|
GET |
/goals/{goalId}/comments |
comments:read |
GET |
/objectives/{objectiveId}/comments |
comments:read |
GET |
/meetings/{meetingId}/comments |
comments:read |
GET |
/actions/{actionId}/comments |
comments:read |
GET |
/ideas/{ideaId}/comments |
comments:read |
POST |
/goals/{goalId}/comments |
comments:write |
POST |
/objectives/{objectiveId}/comments |
comments:write |
POST |
/meetings/{meetingId}/comments |
comments:write |
POST |
/actions/{actionId}/comments |
comments:write |
POST |
/ideas/{ideaId}/comments |
comments:write |
DELETE |
/goals/{goalId}/comments/{commentId} |
comments:write |
DELETE |
/objectives/{objectiveId}/comments/{commentId} |
comments:write |
DELETE |
/meetings/{meetingId}/comments/{commentId} |
comments:write |
DELETE |
/actions/{actionId}/comments/{commentId} |
comments:write |
DELETE |
/comments/{id}?type=… |
comments:write |
Comment changes don't fire webhooks.
The parent must exist in your organization and be visible to the API (not an archived Meeting, a hidden Idea, or an Action whose Goal or Objective is archived), or every comment endpoint for it returns 404.
The JavaScript examples assume a headers object holding your key:
const headers = {
'X-API-Key': process.env.GPS_API_KEY_ID,
'X-API-Secret': process.env.GPS_API_SECRET,
};
The comment object#
Comments on every kind of parent come back in the same shape.
| Field | Type | Notes |
|---|---|---|
id |
string | Comment ID. |
content |
string | The comment text. |
authorId |
string or null | Member ID of the author. |
createdAt |
string | ISO 8601 timestamp. |
updatedAt |
string | ISO 8601 timestamp. For comments on Actions and Ideas this equals createdAt. |
List comments#
GET /goals/{goalId}/comments
GET /objectives/{objectiveId}/comments
GET /meetings/{meetingId}/comments
GET /actions/{actionId}/comments
GET /ideas/{ideaId}/comments
Returns the parent's comments. Comments on Ideas come oldest first; for other parents order isn't guaranteed, so sort by createdAt.
Scope: comments:read
| Parameter | In | Notes |
|---|---|---|
goalId, objectiveId, meetingId, actionId or ideaId |
path | The parent's ID. |
limit, offset |
query | See Pagination. |
curl https://plan.empiraa.app/api/v1/goals/a1f3c5e7-2b4d-4f6a-8c9e-0b1d3f5a7c9e/comments -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
curl https://plan.empiraa.app/api/v1/objectives/c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d/comments -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
curl https://plan.empiraa.app/api/v1/meetings/7a9c1e3f-5b6d-4e8a-8b9c-4d5e6f7a8b9c/comments -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
curl https://plan.empiraa.app/api/v1/actions/e5a7c9e1-6f8b-4c2d-8e9f-3a4b5c6d7e8f/comments -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
curl https://plan.empiraa.app/api/v1/ideas/2c4e6a8c-0d1f-4a3b-9c5d-6e7f8a9b0c1d/comments -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
// parent is 'goals', 'objectives', 'meetings', 'actions' or 'ideas'
async function listComments(parent, parentId) {
const res = await fetch(`https://plan.empiraa.app/api/v1/${parent}/${parentId}/comments`, { headers });
const { data, meta } = await res.json();
return data;
}
const comments = await listComments('goals', 'a1f3c5e7-2b4d-4f6a-8c9e-0b1d3f5a7c9e');
Response 200
{
"data": [
{
"id": "8e0a2c4e-6f7b-4d9c-a1b2-7c8d9e0f1a2b",
"content": "Renewals are tracking ahead of plan this month.",
"authorId": "5d7f9b1d-3e5a-4c7e-9f1a-2b3c4d5e6f70",
"createdAt": "2026-11-11T21:04:13.000Z",
"updatedAt": "2026-11-11T21:04:13.000Z"
}
],
"meta": { "total": 1, "limit": 50, "offset": 0 }
}
Errors: 404 NOT_FOUND if the parent doesn't exist, is in another organization, or isn't visible to the API; 422 VALIDATION_ERROR for a bad limit or offset.
Add a comment#
POST /goals/{goalId}/comments
POST /objectives/{objectiveId}/comments
POST /meetings/{meetingId}/comments
POST /actions/{actionId}/comments
POST /ideas/{ideaId}/comments
Scope: comments:write
| Path parameter | Notes |
|---|---|
goalId, objectiveId, meetingId, actionId or ideaId |
The parent's ID. |
Request body for Goals, Objectives, Meetings and Actions
| Field | Type | Required | Allowed values | Notes |
|---|---|---|---|---|
content |
string | Yes | At least 1 character | The comment text. |
onBehalfOf |
string | No | Active member ID | The author. Default: the key's creator. See Attribution. |
Request body for Ideas: the text goes in body, not content.
| Field | Type | Required | Allowed values | Notes |
|---|---|---|---|---|
body |
string | Yes | 1–2,000 characters | The comment text. Returned as content. |
onBehalfOf |
string | No | Active member ID | The author. |
curl -X POST https://plan.empiraa.app/api/v1/goals/a1f3c5e7-2b4d-4f6a-8c9e-0b1d3f5a7c9e/comments \
-H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET" -H "Content-Type: application/json" \
-d '{ "content": "Renewals are tracking ahead of plan this month." }'
curl -X POST https://plan.empiraa.app/api/v1/objectives/c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d/comments \
-H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET" -H "Content-Type: application/json" \
-d '{ "content": "October figures are in." }'
curl -X POST https://plan.empiraa.app/api/v1/meetings/7a9c1e3f-5b6d-4e8a-8b9c-4d5e6f7a8b9c/comments \
-H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET" -H "Content-Type: application/json" \
-d '{ "content": "Please add the churn report to the agenda." }'
curl -X POST https://plan.empiraa.app/api/v1/actions/e5a7c9e1-6f8b-4c2d-8e9f-3a4b5c6d7e8f/comments \
-H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET" -H "Content-Type: application/json" \
-d '{ "content": "Twelve offers sent so far.", "onBehalfOf": "6e8a0c2e-4f6b-4d8f-a0b1-3c4d5e6f7a81" }'
curl -X POST https://plan.empiraa.app/api/v1/ideas/2c4e6a8c-0d1f-4a3b-9c5d-6e7f8a9b0c1d/comments \
-H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET" -H "Content-Type: application/json" \
-d '{ "body": "We could start with the three largest clients." }'
async function addComment(parent, parentId, text, onBehalfOf) {
// Ideas take the text as `body`; every other parent takes `content`.
const payload = parent === 'ideas' ? { body: text } : { content: text };
if (onBehalfOf) payload.onBehalfOf = onBehalfOf;
const res = await fetch(`https://plan.empiraa.app/api/v1/${parent}/${parentId}/comments`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const { data } = await res.json();
return data;
}
await addComment('goals', 'a1f3c5e7-2b4d-4f6a-8c9e-0b1d3f5a7c9e', 'Renewals are tracking ahead of plan this month.');
await addComment('ideas', '2c4e6a8c-0d1f-4a3b-9c5d-6e7f8a9b0c1d', 'We could start with the three largest clients.');
Response 201
{
"data": {
"id": "8e0a2c4e-6f7b-4d9c-a1b2-7c8d9e0f1a2b",
"content": "Renewals are tracking ahead of plan this month.",
"authorId": "5d7f9b1d-3e5a-4c7e-9f1a-2b3c4d5e6f70",
"createdAt": "2026-11-12T03:21:09.402Z",
"updatedAt": "2026-11-12T03:21:09.402Z"
}
}
Errors
| Status | code |
When |
|---|---|---|
| 404 | NOT_FOUND |
The parent doesn't exist, is in another organization, or isn't visible to the API. |
| 422 | VALIDATION_ERROR |
Missing or empty content (or body for Ideas), or an Idea comment over 2,000 characters. |
| 422 | INVALID_USERS |
onBehalfOf isn't an active member. |
| 422 | NO_KEY_OWNER |
No onBehalfOf, and the key's creator is no longer a member. |
Delete a comment#
DELETE /goals/{goalId}/comments/{commentId}
DELETE /objectives/{objectiveId}/comments/{commentId}
DELETE /meetings/{meetingId}/comments/{commentId}
DELETE /actions/{actionId}/comments/{commentId}
Permanently deletes a comment from the parent you name. A key with comments:write can delete any comment in the organization, not only ones it added. To delete a comment on an Idea, use Delete a comment by ID.
Scope: comments:write
| Path parameter | Notes |
|---|---|
goalId, objectiveId, meetingId or actionId |
The parent's ID. |
commentId |
The comment's ID. It must belong to that parent. |
curl -X DELETE https://plan.empiraa.app/api/v1/goals/a1f3c5e7-2b4d-4f6a-8c9e-0b1d3f5a7c9e/comments/8e0a2c4e-6f7b-4d9c-a1b2-7c8d9e0f1a2b -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
curl -X DELETE https://plan.empiraa.app/api/v1/objectives/c7e9a1b3-4d5f-4a6b-9c8d-1e2f3a4b5c6d/comments/8e0a2c4e-6f7b-4d9c-a1b2-7c8d9e0f1a2b -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
curl -X DELETE https://plan.empiraa.app/api/v1/meetings/7a9c1e3f-5b6d-4e8a-8b9c-4d5e6f7a8b9c/comments/8e0a2c4e-6f7b-4d9c-a1b2-7c8d9e0f1a2b -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
curl -X DELETE https://plan.empiraa.app/api/v1/actions/e5a7c9e1-6f8b-4c2d-8e9f-3a4b5c6d7e8f/comments/8e0a2c4e-6f7b-4d9c-a1b2-7c8d9e0f1a2b -H "X-API-Key: $GPS_API_KEY_ID" -H "X-API-Secret: $GPS_API_SECRET"
async function deleteComment(parent, parentId, commentId) {
const res = await fetch(
`https://plan.empiraa.app/api/v1/${parent}/${parentId}/comments/${commentId}`,
{ method: 'DELETE', headers },
);
return res.status === 204;
}
Response 204: no body.
Errors: 404 NOT_FOUND if the parent or the comment doesn't exist, the comment belongs to a different parent, or either is in another organization.
Delete a comment by ID#
DELETE /comments/{id}?type={type}
Deletes a comment when you know its ID and the kind of parent it's on. This is the only way to delete a comment on an Idea.
Scope: comments:write
| Parameter | In | Required | Allowed values |
|---|---|---|---|
id |
path | Yes | The comment's ID. |
type |
query | Yes | goal, objective, meeting, action, idea |
curl -X DELETE "https://plan.empiraa.app/api/v1/comments/9f1b3d5f-7a8c-4e0d-b2c3-8d9e0f1a2b3c?type=idea" \
-H "X-API-Key: $GPS_API_KEY_ID" \
-H "X-API-Secret: $GPS_API_SECRET"
const res = await fetch(
'https://plan.empiraa.app/api/v1/comments/9f1b3d5f-7a8c-4e0d-b2c3-8d9e0f1a2b3c?type=idea',
{ method: 'DELETE', headers },
);
console.log(res.status); // 204
Response 204: no body.
Errors
| Status | code |
When |
|---|---|---|
| 422 | VALIDATION_ERROR |
type is missing or not one of the allowed values. |
| 404 | NOT_FOUND |
No comment of that type has this ID in your organization. |

